* query-lifecycle and started modules-interfaces * query-lifecycle first draft done * module interfaces first draft * rest and intro skeletons * rest and intro done * small edits and links * comments * revisions * cli.md comments * comments * minor edits * better flow for query lifecycle * add transactions into core * hans comments * add transactions into core * checkout master-docs files * deleted some * remove modules readme * cli.md comments * comments * module-interfaces comments * Merge PR #4857: Add Context concept doc * working * working * finish messages and queries * handler * querier * last comments! * punctuation * querier2 * consolidate intro * querier * workiiiing * refactor for new module interface * karoly review * working on baseapp doc * baseapp work * reorg * almost there * finish first draft * remove old files * finish intro * workinnn * initial commit after rebase * query-lifecycle and started modules-interfaces * query-lifecycle first draft done * module interfaces first draft * rest and intro skeletons * rest and intro done * small edits and links * comments * revisions * cli.md comments * comments * minor edits * better flow for query lifecycle * checkout master-docs files * deleted some * remove modules readme * cli.md comments * comments * module-interfaces comments * keeper * genesis * finish * Apply suggestions from code review Co-Authored-By: Hans Schoenburg <hschoenburg@users.noreply.github.com> * hans review * Update docs/core/baseapp.md Co-Authored-By: Hans Schoenburg <hschoenburg@users.noreply.github.com> * working * last comment * workin * Apply suggestions from code review * encoding and node * almost finish store * finish docs * fixes * fede comments + permalinks * hans review * add more permalinks * update docs theme version (#5239) * R4R: Docs Cleanup (#5246) * start * work * work * work * remove table of content * links intro * fix links * remove junk * cleanup * cleanup * work * finish cleanup * addback readmes * remove nft * fix links * remove dup * remove dup * remove dup * remove dup * remove dup * fix links * add subscribe events * refine rest * index page * sidebar * theme version * theme version * testing netlify * theme version * tooltip example * version * testing code embedding * reverting back * theme version * version * version * version * readme and version * cleanup * redo app anatomy * modules readme, theme version * theme version * fix modules list * theme version * new snippets * modules readme * update docs readme * modify synopsis * version * fix yaml * version * version * version * version * version * version * version * version * version * version * add hide banner * version * version * version * small fixes * modules readme, version * remove hotkeys dep, version * version * version * version * version * version * version * version * slight notice * fix links and hide * permalinks * small clean * version * resolve conflicts, add google analytics * fix merge remants * version * changelog 1/2 * Changelog: docs UI * version * remove merge conflicts * Code: Update link for Contributing to the docs to docs_readme * HTML/CSS: Update layout of homepage footer to match new layout in Figma * version * final modifs * modules, version * modules readme * link to module list from homepage * version * building modules link * version * version * fonts * version * version * fix link * fix package.json * links in explore sdk section * core concepts * version * change delimeters for frontmatter * frontmatter in comments * version * temp add tiny-cookie * fixed link issues * fixed styling issues, copy * hide frontmatter * hide frontmatter * layout fixes, padded ascii diagram * fira sans font for code * scrollbar color * move synopsis from frontmatter * move synopsis from frontmatter * code styling in synopsis fix * tutorial link * active headers * 404 fix * homepage links fix * fix link in footer * misc fixes * version * prerequisite links on mobile * version * version * Fix footer links * version * Fix permalink popup * Update version * package-lock.json * Update Questions section in the footer * Config for Algolia Docsearch * Update version * Update to the latest version of the theme * Use docs-staging as a branch for staging website * Update version * Add google analytics * Remove {hide} from Pre-Requisite Readings * Replace Riot with Discord Co-Authored-By: billy rennekamp <billy.rennekamp@gmail.com> * Fix yaml syntax error in docs * Fix formatting in keyring.md Co-authored-by: Gloria Zhao <gzhao408@berkeley.edu> Co-authored-by: gamarin <gautier@tendermint.com> Co-authored-by: Jack Zampolin <jack.zampolin@gmail.com> Co-authored-by: Hans Schoenburg <hschoenburg@users.noreply.github.com> Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com> Co-authored-by: billy rennekamp <billy.rennekamp@gmail.com>
27 KiB
Anatomy of an SDK Application
This document describes the core parts of a Cosmos SDK application. Throughout the document, a placeholder application named app will be used. {synopsis}
Node Client
The Daemon, or Full-Node Client, is the core process of an SDK-based blockchain. Participants in the network run this process to initialize their state-machine, connect with other full-nodes and update their state-machine as new blocks come in.
^ +-------------------------------+ ^
| | | |
| | State-machine = Application | |
| | | | Built with Cosmos SDK
| | ^ + | |
| +----------- | ABCI | ----------+ v
| | + v | ^
| | | |
Blockchain Node | | Consensus | |
| | | |
| +-------------------------------+ | Tendermint Core
| | | |
| | Networking | |
| | | |
v +-------------------------------+ v
The blockchain full-node presents itself as a binary, generally suffixed by -d for "daemon" (e.g. appd for app or gaiad for gaia). This binary is built by running a simple main.go function placed in ./cmd/appd/. This operation usually happens through the Makefile.
Once the main binary is built, the node can be started by running the start command. This command function primarily does three things:
- Create an instance of the state-machine defined in
app.go. - Initialize the state-machine with the latest known state, extracted from the
dbstored in the~/.appd/datafolder. At this point, the state-machine is at heightappBlockHeight. - Create and start a new Tendermint instance. Among other things, the node will perform a handshake with its peers. It will get the latest
blockHeightfrom them, and replay blocks to sync to this height if it is greater than the localappBlockHeight. IfappBlockHeightis0, the node is starting from genesis and Tendermint sends anInitChainmessage via the ABCI to theapp, which triggers theInitChainer.
Core Application File
In general, the core of the state-machine is defined in a file called app.go. It mainly contains the type definition of the application and functions to create and initialize it.
Type Definition of the Application
The first thing defined in app.go is the type of the application. It is generally comprised of the following parts:
- A reference to
baseapp. The custom application defined inapp.gois an extension ofbaseapp. When a transaction is relayed by Tendermint to the application,appusesbaseapp's methods to route them to the appropriate module.baseappimplements most of the core logic for the application, including all the ABCI methods and the routing logic. - A list of store keys. The store, which contains the entire state, is implemented as a
multistore(i.e. a store of stores) in the Cosmos SDK. Each module uses one or multiple stores in the multistore to persist their part of the state. These stores can be accessed with specific keys that are declared in theapptype. These keys, along with thekeepers, are at the heart of the object-capabilities model of the Cosmos SDK. - A list of module's
keepers. Each module defines an abstraction calledkeeper, which handles reads and writes for this module's store(s). Thekeeper's methods of one module can be called from other modules (if authorized), which is why they are declared in the application's type and exported as interfaces to other modules so that the latter can only access the authorized functions. - A reference to a
codec. The application'scodecis used to serialize and deserialize data structures in order to store them, as stores can only persist[]bytes. Thecodecmust be deterministic. The default codec is amino. - A reference to a module manager and a basic module manager. The module manager is an object that contains a list of the application's module. It facilitates operations related to these modules, like registering
routes, query routes or setting the order of execution between modules for various functions likeInitChainer,BeginBlockerandEndBlocker.
See an example of application type definition from gaia
+++ 5bc422e686/app/app.go (L87-L115)
Constructor Function
This function constructs a new application of the type defined in the section above. It must fulfill the AppCreator signature in order to be used in the start command of the application's daemon command.
+++ 7d7821b9af/server/constructors.go (L20)
Here are the main actions performed by this function:
- Instantiate a new
codecand initialize thecodecof each of the application's module using the basic manager - Instantiate a new application with a reference to a
baseappinstance, a codec and all the appropriate store keys. - Instantiate all the
keepers defined in the application'stypeusing theNewKeeperfunction of each of the application's modules. Note thatkeepersmust be instantiated in the correct order, as theNewKeeperof one module might require a reference to another module'skeeper. - Instantiate the application's module manager with the
AppModuleobject of each of the application's modules. - With the module manager, initialize the application's
routesand query routes. When a transaction is relayed to the application by Tendermint via the ABCI, it is routed to the appropriate module'shandlerusing the routes defined here. Likewise, when a query is received by the application, it is routed to the appropriate module'squerierusing the query routes defined here. - With the module manager, register the application's modules' invariants. Invariants are variables (e.g. total supply of a token) that are evaluated at the end of each block. The process of checking invariants is done via a special module called the
InvariantsRegistry. The value of the invariant should be equal to a predicted value defined in the module. Should the value be different than the predicted one, special logic defined in the invariant registry will be triggered (usually the chain is halted). This is useful to make sure no critical bug goes unnoticed and produces long-lasting effects that would be hard to fix. - With the module manager, set the order of execution between the
InitGenesis,BegingBlockerandEndBlockerfunctions of each of the application's modules. Note that not all modules implement these functions. - Set the remainer of application's parameters:
InitChainer: used to initialize the application when it is first started.BeginBlocker,EndBlocker: called at the beginning and the end of every block).anteHandler: used to handle fees and signature verification.
- Mount the stores.
- Return the application.
Note that this function only creates an instance of the app, while the actual state is either carried over from the ~/.appd/data folder if the node is restarted, or generated from the genesis file if the node is started for the first time.
See an example of application constructor from gaia:
+++ f41a660cdd/app/app.go (L110-L222)
InitChainer
The InitChainer is a function that initializes the state of the application from a genesis file (i.e. token balances of genesis accounts). It is called when the application receives the InitChain message from the Tendermint engine, which happens when the node is started at appBlockHeight == 0 (i.e. on genesis). The application must set the InitChainer in its constructor via the SetInitChainer method.
In general, the InitChainer is mostly composed of the InitGenesis function of each of the application's modules. This is done by calling the InitGenesis function of the module manager, which in turn will call the InitGenesis function of each of the modules it contains. Note that the order in which the modules' InitGenesis functions must be called has to be set in the module manager using the module manager's SetOrderInitGenesis method. This is done in the application's constructor, and the SetOrderInitGenesis has to be called before the SetInitChainer.
See an example of an InitChainer from gaia:
+++ f41a660cdd/app/app.go (L235-L239)
BeginBlocker and EndBlocker
The SDK offers developers the possibility to implement automatic execution of code as part of their application. This is implemented through two function called BeginBlocker and EndBlocker. They are called when the application receives respectively the BeginBlock and EndBlock messages from the Tendermint engine, which happens at the beginning and at the end of each block. The application must set the BeginBlocker and EndBlocker in its constructor via the SetBeginBlocker and SetEndBlocker methods.
In general, the BeginBlocker and EndBlocker functions are mostly composed of the BeginBlock and EndBlock functions of each of the application's modules. This is done by calling the BeginBlock and EndBlock functions of the module manager, which in turn will call the BeginBLock and EndBlock functions of each of the modules it contains. Note that the order in which the modules' BegingBlock and EndBlock functions must be called has to be set in the module manager using the SetOrderBeginBlock and SetOrderEndBlock methods respectively. This is done via the module manager in the application's constructor, and the SetOrderBeginBlock and SetOrderEndBlock methods have to be called before the SetBeginBlocker and SetEndBlocker functions.
As a sidenote, it is important to remember that application-specific blockchains are deterministic. Developers must be careful not to introduce non-determinism in BeginBlocker or EndBlocker, and must also be careful not to make them too computationally expensive, as gas does not constrain the cost of BeginBlocker and EndBlocker execution.
See an example of BeginBlocker and EndBlocker functions from gaia
+++ f41a660cdd/app/app.go (L224-L232)
Register Codec
The MakeCodec function is the last important function of the app.go file. The goal of this function is to instantiate a codec cdc (e.g. amino) initialize the codec of the SDK and each of the application's modules using the RegisterCodec function.
To register the application's modules, the MakeCodec function calls RegisterCodec on ModuleBasics. ModuleBasics is a basic manager which lists all of the application's modules. It is instanciated in the init() function, and only serves to easily register non-dependant elements of application's modules (such as codec). To learn more about the basic module manager, click here.
See an example of a MakeCodec from gaia:
+++ f41a660cdd/app/app.go (L64-L70)
Modules
Modules are the heart and soul of SDK applications. They can be considered as state-machines within the state-machine. When a transaction is relayed from the underlying Tendermint engine via the ABCI to the application, it is routed by baseapp to the appropriate module in order to be processed. This paradigm enables developers to easily build complex state-machines, as most of the modules they need often already exist. For developers, most of the work involved in building an SDK application revolves around building custom modules required by their application that do not exist yet, and integrating them with modules that do already exist into one coherent application. In the application directory, the standard practice is to store modules in the x/ folder (not to be confused with the SDK's x/ folder, which contains already-built modules).
Application Module Interface
Modules must implement interfaces defined in the Cosmos SDK, AppModuleBasic and AppModule. The former implements basic non-dependant elements of the module, such as the codec, while the latter handles the bulk of the module methods (including methods that require references to other modules' keepers). Both the AppModule and AppModuleBasic types are defined in a file called ./module.go.
AppModule exposes a collection of useful methods on the module that facilitates the composition of modules into a coherent application. These methods are are called from the module manager(../building-modules/module-manager.md#manager), which manages the application's collection of modules.
Message Types
Messages are objects defined by each module that implement the message interface. Each transaction contains one or multiple messages.
When a valid block of transactions is received by the full-node, Tendermint relays each one to the application via DeliverTx. Then, the application handles the transaction:
- Upon receiving the transaction, the application first unmarshalls it from
[]bytes. - Then, it verifies a few things about the transaction like fee payment and signatures before extracting the message(s) contained in the transaction.
- With the
Type()method of themessage,baseappis able to route it to the appropriate module'shandlerin order for it to be processed. - If the message is successfully processed, the state is updated.
For a more detailed look at a transaction lifecycle, click here.
Module developers create custom message types when they build their own module. The general practice is to prefix the type declaration of the message with Msg. For example, the message type MsgSend allows users to transfer tokens:
+++ 7d7821b9af/x/bank/internal/types/msgs.go (L10-L15)
It is processed by the handler of the bank module, which ultimately calls the keeper of the auth module in order to update the state.
Handler
The handler refers to the part of the module responsible for processing the message after it is routed by baseapp. handler functions of modules are only executed if the transaction is relayed from Tendermint by the DeliverTx ABCI message. If the transaction is relayed by CheckTx, only stateless checks and fee-related stateful checks are performed. To better understand the difference between DeliverTxand CheckTx, as well as the difference between stateful and stateless checks, click here.
The handler of a module is generally defined in a file called handler.go and consists of:
- A switch function
NewHandlerto route the message to the appropriatehandlerfunction. This function returns ahandlerfunction, and is registered in theAppModuleto be used in the application's module manager to initialize the application's router. Next is an example of such a switch from the nameservice tutorial +++ https://github.com/cosmos/sdk-tutorials/blob/master/nameservice/x/nameservice/handler.go#L12-L26 - One handler function for each message type defined by the module. Developers write the message processing logic in these functions. This generally involves doing stateful checks to ensure the message is valid and calling
keeper's methods to update the state.
Handler functions return a result of type sdk.Result, which informs the application on whether the message was successfully processed:
+++ 7d7821b9af/types/result.go (L15-L40)
Querier
Queriers are very similar to handlers, except they serve user queries to the state as opposed to processing transactions. A query is initiated from an interface by an end-user who provides a queryRoute and some data. The query is then routed to the correct application's querier by baseapp's handleQueryCustom method using queryRoute:
+++ 7d7821b9af/baseapp/abci.go (L395-L453)
The Querier of a module is defined in a file called querier.go, and consists of:
- A switch function
NewQuerierto route the query to the appropriatequerierfunction. This function returns aquerierfunction, and is is registered in theAppModuleto be used in the application's module manager to initialize the application's query router. See an example of such a switch from the nameservice tutorial: +++86a27321cf/nameservice/x/nameservice/internal/keeper/querier.go (L19-L32) - One querier function for each data type defined by the module that needs to be queryable. Developers write the query processing logic in these functions. This generally involves calling
keeper's methods to query the state and marshalling it to JSON.
Keeper
Keepers are the gatekeepers of their module's store(s). To read or write in a module's store, it is mandatory to go through one of its keeper's methods. This is ensured by the object-capabilities model of the Cosmos SDK. Only objects that hold the key to a store can access it, and only the module's keeper should hold the key(s) to the module's store(s).
Keepers are generally defined in a file called keeper.go. It contains the keeper's type definition and methods.
The keeper type definition generally consists of:
- Key(s) to the module's store(s) in the multistore.
- Reference to other module's
keepers. Only needed if thekeeperneeds to access other module's store(s) (either to read or write from them). - A reference to the application's codec. The
keeperneeds it to marshal structs before storing them, or to unmarshal them when it retrieves them, because stores only accept[]bytesas value.
Along with the type definition, the next important component of the keeper.go file is the keeper's constructor function, NewKeeper. This function instantiates a new keeper of the type defined above, with a codec, store keys and potentially references to other modules' keepers as parameters. The NewKeeper function is called from the application's constructor. The rest of the file defines the keeper's methods, primarily getters and setters.
Command-Line and REST Interfaces
Each module defines command-line commands and REST routes to be exposed to end-user via the application's interfaces. This enables end-users to create messages of the types defined in the module, or to query the subset of the state managed by the module.
CLI
Generally, the commands related to a module are defined in a folder called client/cli in the module's folder. The CLI divides commands in two category, transactions and queries, defined in client/cli/tx.go and client/cli/query.go respectively. Both commands are built on top of the Cobra Library:
- Transactions commands let users generate new transactions so that they can be included in a block and eventually update the state. One command should be created for each message type defined in the module. The command calls the constructor of the message with the parameters provided by the end-user, and wraps it into a transaction. The SDK handles signing and the addition of other transaction metadata.
- Queries let users query the subset of the state defined by the module. Query commands forward queries to the application's query router, which routes them to the appropriate querier the
queryRouteparameter supplied.
REST
The module's REST interface lets users generate transactions and query the state through REST calls to the application's light client daemon (LCD). REST routes are defined in a file client/rest/rest.go, which is composed of:
- A
RegisterRoutesfunction, which registers each route defined in the file. This function is called from the main application's interface for each module used within the application. The router used in the SDK is Gorilla's mux. - Custom request type definitions for each query or transaction creation function that needs to be exposed. These custom request types build on the base
requesttype of the Cosmos SDK: +++7d7821b9af/types/rest/rest.go (L47-L60) - One handler function for each request that can be routed to the given module. These functions implement the core logic necessary to serve the request.
Application Interface
Interfaces let end-users interact with full-node clients. This means querying data from the full-node or creating and sending new transactions to be relayed by the full-node and eventually included in a block.
The main interface is the Command-Line Interface. The CLI of an SDK application is built by aggregating CLI commands defined in each of the modules used by the application. The CLI of an application generally has the -cli suffix (e.g. appcli), and defined in a file called cmd/appcli/main.go. The file contains:
- A
main()function, which is executed to build theappcliinterface client. This function prepares each command and adds them to therootCmdbefore building them. At the root ofappCli, the function adds generic commands likestatus,keysandconfig, query commands, tx commands andrest-server. - Query commands are added by calling the
queryCmdfunction, also defined inappcli/main.go. This function returns a Cobra command that contains the query commands defined in each of the application's modules (passed as an array ofsdk.ModuleClientsfrom themain()function), as well as some other lower level query commands such as block or validator queries. Query command are called by using the commandappcli query [query]of the CLI. - Transaction commands are added by calling the
txCmdfunction. Similar toqueryCmd, the function returns a Cobra command that contains the tx commands defined in each of the application's modules, as well as lower level tx commands like transaction signing or broadcasting. Tx commands are called by using the commandappcli tx [tx]of the CLI. - A
registerRoutesfunction, which is called from themain()function when initializing the application's light-client daemon (LCD) (i.e.rest-server).registerRoutescalls theRegisterRoutesfunction of each of the application's module, thereby registering the routes of the module to the lcd's router. The LCD can be started by running the following commandappcli rest-server.
See an example of an application's main command-line file from the nameservice tutorial
+++ 86a27321cf/nameservice/cmd/nscli/main.go
Dependencies and Makefile
This section is optional, as developers are free to choose their dependency manager and project building method. That said, the current most used framework for versioning control is go.mod. It ensures each of the libraries used throughout the application are imported with the correct version. See an example from the nameservice tutorial:
+++ c6754a1e31/go.mod (L1-L18)
For building the application, a Makefile is generally used. The Makefile primarily ensures that the go.mod is run before building the two entrypoints to the application, appd and appcli. See an example of Makefile from the nameservice tutorial
+++ 86a27321cf/nameservice/Makefile
Next {hide}
Learn more about the Lifecycle of a transaction {hide}