From 5fea67d59cd05754cbbff806f877719bece1acf6 Mon Sep 17 00:00:00 2001 From: testinginprod <98415576+testinginprod@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:32:40 +0200 Subject: [PATCH] docs(x/accounts): init of the x/accounts module docs. (#22099) --- x/accounts/README.md | 394 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 370 insertions(+), 24 deletions(-) diff --git a/x/accounts/README.md b/x/accounts/README.md index 6be5a541eaeb..a7490a9fcc65 100644 --- a/x/accounts/README.md +++ b/x/accounts/README.md @@ -1,8 +1,356 @@ -# x/accounts Module +# x/accounts The x/accounts module enhances the Cosmos SDK by providing tools and infrastructure for creating advanced smart accounts. -# The Authentication Interface +## Basics + +An account can be thought of as a simplified cosmos-sdk module that supports multiple deployments. This means: + +1. A single account implementation can be deployed to multiple addresses, similar to how CosmWasm allows multiple contract instances from one WASM upload. + +2. Each account address is mapped to its corresponding account code. + +3. Accounts maintain their own state partition, similar to modules. + +4. Accounts can define both message and query handlers. + +This design allows for flexible and reusable account structures within the ecosystem. + +### Example account creation + +#### Basic + +Defining an account begins with creating a struct that encapsulates the account's state. If the account has no state, the +struct is empty `type Account struct{}`. + +By default, accounts utilize collections to manage their state. + +##### State Isolation + +It's crucial to understand that an account's state is isolated. This means: + +1. States are not shared between accounts of different types. +2. States are not shared even between accounts of the same type. + +For example, consider two accounts of type Counter: + +- One located at address "cosmos123" +- Another at address "cosmos456" + +These accounts do not share the same collections.Item instance. Instead, each maintains its own separate state. + +```go +type Account struct { + // We will define that the account contains in its state a counter, it's an item. + // It could also be a map or whatever! + Counter collections.Item[uint64] +} +``` + +#### Init + +Creating an account begins with defining its init message. This message is processed when an account is created, similar to: + +- The `instantiate` method in a CosmWasm contract +- The `constructor` in an EVM contract + +For an account to be a valid `x/account` implementer, it must define both: + +1. An `Init` method +2. An init message + +We start by defining the `MsgInit` and its corresponding `MsgInitResponse` as protobuf messages: + +```protobuf +message MsgInit { + uint64 counter = 1; +} + +message MsgInitResponse {} +``` + +Next, we implement the Init method, which sets the initial counter. We also implement a method of the `Account` interface. This method: + +Signals to the x/accounts runtime what the Init entrypoint is +Performs some generic operations to maintain type safety in the system + +Here's the Go implementation: + +```go +package counter + +import ( + "context" + "cosmossdk.io/x/accounts/accountstd" +) + +type Account struct { + Counter collections.Item[uint64] +} + +func (a Account) Init(ctx context.Context, msg *MsgInit) (*MsgInitResponse, error) { + err := a.Counter.Set(ctx, msg.Counter) + if err != nil { + return nil, err + } + + return &MsgInitResponse{}, nil +} + +func (a Account) RegisterInitHandler(builder *accountstd.InitBuilder) { + accountstd.RegisterInitHandler(builder, a.Init) +} +``` + +#### Execute Handlers + +Execute handlers are methods that an account can execute, defined as messages. These executions can be triggered: + +- During block execution (not queries) through transactions +- During begin or end block + +To define an execute handler, we start by creating its proto message: + +```protobuf +message MsgIncreaseCounter { + uint64 amount = 1; +} + +message MsgIncreaseCounterResponse { + uint64 new_value = 1; +} +``` + +Next, we implement the handling code for this message and register it using the `RegisterExecuteHandlers` method: + +```go +package counter + +import ( + "context" + "cosmossdk.io/x/accounts/accountstd" +) + +type Account struct { + Counter collections.Item[uint64] +} + +func (a Account) Init(ctx context.Context, msg *MsgInit) (*MsgInitResponse, error) { + err := a.Counter.Set(ctx, msg.Counter) + if err != nil { + return nil, err + } + return &MsgInitResponse{}, nil +} + +// Handler for MsgIncreaseCounter +func (a Account) IncreaseCounter(ctx context.Context, msg *MsgIncreaseCounter) (*MsgIncreaseCounterResponse, error) { + counter, err := a.Counter.Get(ctx) + if err != nil { + return nil, err + } + + newValue := counter + msg.Amount + err = a.Counter.Set(ctx, newValue) + if err != nil { + return nil, err + } + + return &MsgIncreaseCounterResponse{NewValue: newValue}, nil +} + +// Registration of the handler in the runtime +func (a Account) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { + accountstd.RegisterExecuteHandler(builder, a.IncreaseCounter) +} + +func (a Account) RegisterInitHandler(builder *accountstd.InitBuilder) { + accountstd.RegisterInitHandler(builder, a.Init) +} +``` + +This implementation defines an IncreaseCounter method that handles the MsgIncreaseCounter message, updating the counter +value and returning the new value in the response. + +#### Query Handlers + +Query Handlers are read-only methods implemented by an account to expose information about itself. This information can be accessed by: + +- External clients (e.g., CLI, wallets) +- Other modules and accounts within the system + +Query handlers can be invoked: + +1. By external clients +2. During block execution + +To define a query handler, we follow a similar process to execute handlers: + +1. Define the request and response proto messages: + +```protobuf +message QueryCounter {} + +message QueryCounterResponse { + uint64 value = 1; +} +``` + +2. Implement and register the query handler: + +```go +package counter + +import ( + "context" + "cosmossdk.io/x/accounts/accountstd" +) + +func (a Account) QueryCounter(ctx context.Context, _ *QueryCounterRequest) (*QueryCounterResponse, error) { + counter, err := a.Counter.Get(ctx) + if err != nil { + return nil, err + } + return &QueryCounterResponse{ + Value: counter, + }, nil +} + +func (a Account) RegisterQueryHandlers(builder *accountstd.QueryBuilder) { + accountstd.RegisterQueryHandler(builder, a.QueryCounter) +} +``` + +This implementation defines a `QueryCounter` method that retrieves the current counter value and returns it in the response. +The `RegisterQueryHandlers` method registers this query handler with the system. + +#### The Account Constructor + +After creating our basic counter account, we implement the account constructor function: + +```go +package counter + +import ( + "cosmossdk.io/collections" + "cosmossdk.io/x/accounts/accountstd" +) + +func NewAccount(deps accountstd.Dependencies) (Account, error) { + return Account{ + Counter: collections.NewItem(deps.SchemaBuilder, CounterPrefix, "counter", collections.Uint64Value), + }, nil +} + +type Account struct { + Counter collections.Item[uint64] +} + +// Rest of the Account implementation... +``` + +The `accountstd.Dependencies` type provides an environment with essential components: + +1. `AddressCodec`: For encoding and decoding addresses +2. `SchemaBuilder`: For schema construction (handled by the accounts module) +3. `HeaderService`: For accessing block header information +4. Other useful services and utilities + +These dependencies allow the account to interact with the blockchain system. + +## App Wiring + +Note: This assumes you've already wired the `x/accounts` module in your application. If not, refer to the Simapp example. + +After creating our basic account, we wire it to the `x/accounts` module. + +### Depinject Method + +Define the depinject constructor: + +```go +package counterdepinject + +func ProvideAccount() accountstd.DepinjectAccount { + return accountstd.DIAccount("counter", counter.NewAccount) +} +``` + +Add this to the application: + +```go +package app + +func NewApp() *App { + // ... + appConfig = depinject.Configs( + AppConfig(), + depinject.Supply( + appOpts, + logger, + ), + depinject.Provide( + counterdepinject.ProvideAccount, + ), + ) + // ... +} +``` + +### Manual Method + +Add the account to the x/accounts Keeper: + +```go +accountsKeeper, err := accounts.NewKeeper( + appCodec, + runtime.NewEnvironment(/* ... */), + signingCtx.AddressCodec(), + appCodec.InterfaceRegistry(), + + accountstd.AddAccount("counter", counter.NewAccount), // Add account here + // Add more accounts if needed +) +``` + +Choose the method that best fits your application structure. + +### The accountsstd Package + +The `accountsstd` package provides utility functions for use within account init, execution, or query handlers. Key functions include: + +1. `Whoami()`: Retrieves the address of the current account. +2. `Sender()`: Gets the address of the transaction sender (not available in queries). +3. `Funds()`: Retrieves funds provided by the sender during Init or Execution. +4. `ExecModule()`: Allows the account to execute a module message. + Note: Impersonation is prevented. An account can't send messages on behalf of others. +5. `QueryModule()`: Enables querying a module. + +These functions, along with others, facilitate account operations and interactions within the system. +For a comprehensive list of available utilities, refer to the Go documentation. + +### Interfaces via Messages and Queries + +Accounts can handle various messages and queries, allowing for flexible interface definitions: + +1. Multiple account types can handle the same message or query. +2. Different accounts (even with the same type but different addresses) can process identical messages or queries. + +This flexibility enables defining interfaces as common sets of messages and/or queries that accounts can handle. + +Example: Transaction Authentication + +- We define a `MsgAuthenticate` message. +- Any account capable of handling `MsgAuthenticate` is considered to implement the `Authentication` interface. +- This approach allows for standardized interaction patterns across different account types. + +(Note: More details on the `Authentication` interface will be provided later.) + +### Full Examples + +Some examples can be found in the [defaults](./defaults) package. + +## The Authentication Interface x/accounts introduces the `Authentication` interface, allowing for flexible transaction (TX) authentication beyond traditional public key cryptography. @@ -14,21 +362,21 @@ The key message type for authentication is `MsgAuthenticate`, which is defined i [interfaces/account_abstraction/v1/interface.proto](./proto/cosmos/accounts/interfaces/account_abstraction/v1/interface.proto) -## Authentication Mechanism +### Authentication Mechanism -### AnteHandler in the SDK +#### AnteHandler in the SDK The Cosmos SDK utilizes an `AnteHandler` to verify transaction (TX) integrity. Its primary function is to ensure that the messages within a transaction are correctly signed by the purported sender. -### Authentication Flow for x/accounts Module +#### Authentication Flow for x/accounts Module When the `AnteHandler` identifies that a message sender (and transaction signer) belongs to the x/accounts module, it delegates the authentication process to that module. -#### Authentication Interface Requirement +##### Authentication Interface Requirement For successful authentication, the account must implement the `Authentication` interface. If an account fails to implement this interface, it's considered non-externally owned, resulting in transaction rejection. -##### Sequence Diagram +###### Sequence Diagram ```mermaid graph TD @@ -43,8 +391,7 @@ graph TD H --> I ``` - -## Implementing the Authentication Interface +### Implementing the Authentication Interface To implement the Authentication interface, an account must handle the execution of `MsgAuthenticate`. Here's an example of how to do this: @@ -80,33 +427,32 @@ func (a Account) RegisterExecuteHandlers(builder *accountstd.ExecuteBuilder) { } ``` -### Key Implementation Points +#### Key Implementation Points 1. **Sender Verification**: Always verify that the sender is the x/accounts module. This prevents unauthorized accounts from triggering authentication. 2. **Authentication Safety**: Ensure your authentication mechanism is secure: - Prevent replay attacks by making it impossible to reuse the same action with the same signature. - -#### Implementation example +##### Implementation example Please find an example [here](./defaults/base/account.go). -# Supporting Custom Accounts in the x/auth gRPC Server +## Supporting Custom Accounts in the x/auth gRPC Server -## Overview +### Overview The x/auth module provides a mechanism for custom account types to be exposed via its `Account` and `AccountInfo` gRPC -queries. This feature is particularly useful for ensuring compatibility with existing wallets that have not yet integrated +queries. This feature is particularly useful for ensuring compatibility with existing wallets that have not yet integrated with x/accounts but still need to parse account information post-migration. -## Implementation +### Implementation To support this feature, your custom account type needs to implement the `auth.QueryLegacyAccount` handler. Here are some important points to consider: 1. **Selective Implementation**: This implementation is not required for every account type. It's only necessary for accounts you want to expose through the x/auth gRPC `Account` and `AccountInfo` methods. 2. **Flexible Response**: The `info` field in the `QueryLegacyAccountResponse` is optional. If your custom account cannot be represented as a `BaseAccount`, you can leave this field empty. -## Example Implementation +### Example Implementation A concrete example of implementation can be found in `defaults/base/account.go`. Here's a simplified version: @@ -143,23 +489,23 @@ func (a Account) AuthRetroCompatibility(ctx context.Context, _ *authtypes.QueryL } ``` -## Usage Notes +### Usage Notes -* Implement this handler only for account types you want to expose via x/auth gRPC methods. -* The `info` field in the response can be nil if your account doesn't fit the `BaseAccount` structure. +- Implement this handler only for account types you want to expose via x/auth gRPC methods. +- The `info` field in the response can be nil if your account doesn't fit the `BaseAccount` structure. -# Genesis +## Genesis -## Creating accounts on genesis +### Creating accounts on genesis In order to create accounts at genesis, the `x/accounts` module allows developers to provide a list of genesis `MsgInit` messages that will be executed in the `x/accounts` genesis flow. The init messages are generated offline. You can also use the following CLI command to generate the -json messages: `simd accounts tx init [account type] [msg] --from me --genesis`. This will generate +json messages: `simd accounts tx init [account type] [msg] --from me --genesis`. This will generate a jsonified init message wrapped in an x/accounts `MsgInit`. -This follows the same initialization flow and rules that would happen if the chain is running. +This follows the same initialization flow and rules that would happen if the chain is running. The only concrete difference is that this is happening at the genesis block. For example, given the following `genesis.json` file: