-
Notifications
You must be signed in to change notification settings - Fork 187
Accounts Refactor PR 1: Adding account -> [credential_id] mapping #2770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
citizen-stig
merged 21 commits into
theodore/multisig-upgrade
from
nikolai/accounts-refactor-part-1
May 6, 2026
Merged
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
c1b3d6c
Adding account -> [credential_id] mapping
citizen-stig f22eb56
Review: part 1
citizen-stig ef0701d
Updating docs and more simplification
citizen-stig 7ade9ef
Reverting dual-write
citizen-stig 256f9a0
Updating documentation
citizen-stig ae53c6e
Update json schemas
citizen-stig 37efa41
PR feedback fixes part 1
citizen-stig 06da3e1
Revert and print receipt on error
citizen-stig 6f04654
Explicitly authorize embedded credential in sov-hyperlane-solana-regi…
citizen-stig 3af9f58
Rollback fluff part 1
citizen-stig 60cc62e
Revert accidental switcheroo
citizen-stig e3bb2c9
Clean up
citizen-stig 8aad53e
Reformat readme for readability
citizen-stig 83d4df0
More info in genesis bail
citizen-stig 91d0e3e
Simplify part n
citizen-stig c87eba4
Initial migrations implementation
citizen-stig acbef4a
Clean up
citizen-stig d1c2bc9
Fix migrations and add custom REST API for accounts module
citizen-stig a274156
Remove legacy account map reading from custom REST API.
citizen-stig 8377e6d
Fixes
citizen-stig 00beac9
Address README.md comments
citizen-stig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
devin-ai-integration[bot] marked this conversation as resolved.
devin-ai-integration[bot] marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 69 additions & 7 deletions
76
crates/module-system/module-implementations/sov-accounts/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,77 @@ | ||
| # `sov-accounts` module | ||
|
|
||
| The `sov-accounts` module is responsible for managing accounts on the rollup. | ||
| The `sov-accounts` module resolves transaction credentials to rollup | ||
| addresses and records which credentials may act for which addresses. | ||
|
|
||
| ### The `sov-accounts` module offers the following functionality | ||
|
|
||
| ### The `sov-accounts` module offers the following functionality: | ||
| 1. A credential has a deterministic canonical address, computed as | ||
| `credential_id.into::<S::Address>()`. This relation is stateless: using the | ||
| canonical address does not require an account entry to be written. | ||
|
|
||
| 1. When a sender sends their first message, the `sov-accounts` module will create a new address by deriving it from the sender's credential. | ||
| The module will then add a mapping between the credential id and the address to its state. For all subsequent messages that include the sender's credential, | ||
| the module will retrieve the sender's address from the mapping and pass it along with the original message to an intended module. | ||
| 1. Legacy state can contain an explicit `credential_id -> address` mapping. | ||
| This map is used for credential-only resolution and the `get_account` query. | ||
|
|
||
| 1. It is possible to add new credential to a given address using the `CallMessage::InsertCredentialId(..)` message. | ||
| 1. It is possible to authorize another credential for the caller's address using | ||
| the `CallMessage::InsertCredentialId(..)` message. This writes an | ||
| `account_owners` authorization, not a credential-indexed account entry. | ||
|
|
||
| 1. It is possible to query the `sov-accounts` module using the `get_account` method and get the account corresponding to the given credential id. | ||
| 1. It is possible to query the `sov-accounts` module using the `get_account` | ||
| method and get the legacy/custom mapped address corresponding to the given | ||
| credential id. | ||
|
|
||
| ## Credential and Address Relations | ||
|
|
||
| The module has three credential/address relations. They answer different | ||
| questions and should not be treated as interchangeable. | ||
|
|
||
| ### Stateless canonical address | ||
|
|
||
| ```text | ||
| credential_id -> credential_id.into::<S::Address>() | ||
| ``` | ||
|
|
||
| This is the default address for a credential. It is deterministic and requires | ||
| no state write. If a credential has no explicit credential-indexed state | ||
| mapping, this canonical address is the natural fallback for credential-only | ||
| routing. | ||
|
|
||
| ### Credential-to-account map | ||
|
|
||
| ```text | ||
| accounts[credential_id] = Account { addr } | ||
| ``` | ||
|
|
||
| This legacy/custom state map records the primary address explicitly associated | ||
| with a credential. A credential has at most one primary address in this map, | ||
| while one address may be the primary address for many credentials. | ||
|
|
||
| This map is used when callers only know a `CredentialId` and need an address: | ||
| `resolve_sender_address`, `get_account`, and legacy/custom account mappings all | ||
| depend on this credential-indexed lookup. New `InsertCredentialId` calls do not | ||
| write this map. `get_account` reports entries from this explicit map; it does | ||
| not mean that every possible stateless canonical address or explicit | ||
| authorization has a stored account entry. | ||
|
|
||
| ### Account-credential authorization map | ||
|
|
||
| ```text | ||
| account_owners[(address, credential_id)] = true | ||
| ``` | ||
|
|
||
| This state map records authorization. A present entry means the credential is | ||
| authorized to sign transactions that execute as the given address. The key is | ||
| the exact `(address, credential_id)` pair, so this relation does not provide a | ||
| credential-only lookup by itself. | ||
|
|
||
| This relation answers "may this credential act as this address?" once the target | ||
| address is known. It is not a replacement for the credential-to-account map, | ||
| because it is keyed by `(address, credential_id)` and cannot efficiently answer | ||
| "which address is this credential mapped to?" with a single point lookup. | ||
| New `InsertCredentialId` calls write this relation. | ||
|
|
||
| In short: routing from only a credential uses the explicit credential-to-account | ||
| map or the stateless canonical fallback. Callers that need to verify whether a | ||
| known address may be used with a credential should use the combined | ||
| `is_authorized_for` check: legacy/custom mapping, stateless canonical address, | ||
| or `account_owners`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 63 additions & 22 deletions
85
crates/module-system/module-implementations/sov-accounts/src/capabilities.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,86 @@ | ||
| use sov_modules_api::{CredentialId, Spec, StateAccessor, StateReader, StateWriter}; | ||
| use sov_state::User; | ||
|
|
||
| use crate::{Account, Accounts}; | ||
| use crate::{AccountOwnerKey, Accounts}; | ||
|
|
||
| impl<S: Spec> Accounts<S> { | ||
| /// Resolve the sender's public key to an address. | ||
| /// If the sender is not registered, but a fallback address if provided, immediately registers | ||
| /// the credential to the fallback and then returns it. | ||
| /// Authorizes `credential_id` to sign as `address`. | ||
| pub(crate) fn authorize_credential<ST: StateWriter<User>>( | ||
| &mut self, | ||
| address: &S::Address, | ||
| credential_id: &CredentialId, | ||
| state: &mut ST, | ||
| ) -> Result<(), <ST as StateWriter<User>>::Error> { | ||
| self.account_owners.set( | ||
| &AccountOwnerKey::new(*address, *credential_id), | ||
| &true, | ||
| state, | ||
| ) | ||
| } | ||
|
citizen-stig marked this conversation as resolved.
|
||
|
|
||
| /// Resolve the sender's credential to an address. | ||
| /// If `credential_id` has a legacy/custom mapping in `accounts`, return it. | ||
| /// Otherwise, return the supplied `default_address` without writing account state. | ||
| pub fn resolve_sender_address<ST: StateAccessor>( | ||
| &mut self, | ||
| default_address: &S::Address, | ||
| credential_id: &CredentialId, | ||
| state: &mut ST, | ||
| ) -> Result<S::Address, <ST as StateWriter<User>>::Error> { | ||
| let maybe_address = self.accounts.get(credential_id, state)?.map(|a| a.addr); | ||
|
|
||
| match maybe_address { | ||
| Some(address) => Ok(address), | ||
| None => { | ||
| // 1. Add the credential -> account mapping | ||
| let new_account = Account { | ||
| addr: *default_address, | ||
| }; | ||
| self.accounts.set(credential_id, &new_account, state)?; | ||
|
|
||
| Ok(*default_address) | ||
| } | ||
| if let Some(account) = self.accounts.get(credential_id, state)? { | ||
| return Ok(account.addr); | ||
| } | ||
| Ok(*default_address) | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
citizen-stig marked this conversation as resolved.
Outdated
|
||
|
|
||
| /// Resolve the sender's public key to an address. | ||
| /// Read-only variant of [`Self::resolve_sender_address`]: returns | ||
| /// `default_address` when the credential has no legacy/custom mapping, | ||
| /// without writing. | ||
| pub fn resolve_sender_address_read_only<ST: StateReader<User>>( | ||
| &self, | ||
| default_address: &S::Address, | ||
| credential_id: &CredentialId, | ||
| state: &mut ST, | ||
| ) -> Result<S::Address, ST::Error> { | ||
| let maybe_address = self.accounts.get(credential_id, state)?.map(|a| a.addr); | ||
| match maybe_address { | ||
| Some(address) => Ok(address), | ||
| None => Ok(*default_address), | ||
| if let Some(account) = self.accounts.get(credential_id, state)? { | ||
| return Ok(account.addr); | ||
| } | ||
| Ok(*default_address) | ||
| } | ||
|
|
||
| /// Returns `true` if `credential_id` has an explicit `account_owners` | ||
| /// authorization for `address`. | ||
| pub fn is_authorized<ST: StateReader<User>>( | ||
| &self, | ||
| address: &S::Address, | ||
| credential_id: &CredentialId, | ||
| state: &mut ST, | ||
| ) -> Result<bool, ST::Error> { | ||
| Ok(self | ||
| .account_owners | ||
| .get(&AccountOwnerKey::new(*address, *credential_id), state)? | ||
| .is_some()) | ||
| } | ||
|
|
||
| /// Returns `true` if `credential_id` is authorized to act as `address` | ||
| /// under any supported relation: legacy/custom `accounts` mapping, | ||
| /// stateless canonical address, or explicit `account_owners` | ||
| /// authorization. | ||
| pub fn is_authorized_for<ST: StateReader<User>>( | ||
| &self, | ||
| address: &S::Address, | ||
| credential_id: &CredentialId, | ||
| state: &mut ST, | ||
| ) -> Result<bool, ST::Error> { | ||
| if let Some(account) = self.accounts.get(credential_id, state)? { | ||
| return Ok(account.addr == *address); | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| let canonical_address: S::Address = (*credential_id).into(); | ||
| if canonical_address == *address { | ||
| return Ok(true); | ||
| } | ||
|
|
||
| self.is_authorized(address, credential_id, state) | ||
| } | ||
|
devin-ai-integration[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.