What are accounts?
The Accounts CorDapp allows a CorDapp developer to split a Corda node's vault up into logical sub-vaults. For further information please watch this video.
Not all states stored in a node's vault need to be allocated to an account. Only those states which are held by
PublicKeys assigned to an account ID are held by accounts.
for now, care must be taken when using accounts and non-accounts workflows on the same node. The recommendation is if you wish to use accounts, then use accounts in all of your workflows. This means that each node operates at least one account.
Accounts and the
The base building block for the Accounts CorDapp is the
AccountInfo state. It is a
ContractState, so can be shared
with other nodes and contains basic information about an account:
- Account host (
Party) which is used to map
PublicKeys to a host node
- Account name (
String) which is usually a human readable string to identify the account. It must be unique at the account host level but may not be unique at the network level. The tuple of account host and account name is guaranteed to be unique at the network level.
- Account ID (
UUID) which is a 128-bit random ID that should be unique at the network level. The account ID is used by Corda to map
PublicKeys to accounts.
For the time being, unlike regular Corda nodes which have a default key pair, the Accounts CorDapp intentionally doesn't
create a new key pair and associate the
PublicKey as the default key for a new account. Instead, when you wish to
transact with an account the
RequestKey flow should be used to request that an account on a local or remote node
generate a new key pair and send back the
PublicKey for use in a transaction. Each time you wish to transact with an
account, a new
PublicKey should be requested.
AccountInfo states can be shared with other nodes using the
ShareAccountInfoFlow and requested from other nodes
RequestAccountInfoFlow. These flows will be covered in more detail later on in the documentation.
There are also initiating versions of these flows which can be startable by a
CordaServiceor via RPC. The naming convention is such that in-line sub flows all have the word
Flowas a suffix and the initiating versions of the flows startable via RPC and service have the
In this light,
AccountInfos act as a directory entry for accounts hosted on the local node as well as any other remote
node. It is possible to enumerate all local accounts, or all accounts for a specific remote node, or indeed all accounts
the node in question is aware of. By this point you are probably aware that there is a one-to-many relationship between
node to account: a Corda node can host many accounts and this is depicted particularly well by the image below created
by Peter Li from R3's developer relations team.
The image shows that a node can host many accounts and that:
- an account hosted by a node can transact with an account hosted by another node
- two accounts hosted by the same node can transact with each other
- an account hosted by a node can transact with a regular Corda node
"But what IS an account?"
At this point it is important to remember than an account on a node is just a collection of
PublicKeys which have
been all assigned to the same
UUID, which is the
linearId of the
AccountInfo state (described above). These
PublicKeys can then be used to participate in (or own)
ContractStates and that's how Corda determines which account
ContractState belongs to. When the transaction containing said
ContractState is committed to the ledger, we can
then say that
ContractState is owned or participated in by the account that the
PublicKey is allocated to. When
you wish to query the vault it is possible to specify from which account you wish to query states from.
That's all there is to accounts. To re-iterate and summarise:
AccountInfos are used as an immutable record of the account ID, host and name at the network level
- A host node can create new key pairs and allocate those key pairs to an account they host
- A key pair allocated to an account can be used to participate in a
ContractState- when that state is committed to the ledger with a transaction then we can say that that
ContractStateis owned/participated in by the account to which the
- The vault can be queried on a per account basis to return only the
ContractStates for a specific account or set of accounts.
For further information on how accounts work "under the hood" check out the appendix of this document where there is a video explaining how it all fits together.
What's the difference between an account and a node?
A node is a process that runs
corda.jar. Nodes have a network identity issued to them by the trust root of the
network the node participates in. The node has a vault to store state objects, a transaction store to store transactions
and a flow state machine manager to run flows. The node is essentially a container for running business logic written in
CorDapps. Corda nodes have:
- a "default"
PublicKeywhich is associated with the
CordaX500Namefor the node. Both the
CordaX500Nameare paired with each other inside the
Partyobject for the node.
- the capability to create new key pairs and allocate them to an "external ID" using the
IdentityService. These keys are often called "confidential identities" (
AnonymousPartys) because they have no
CordaX500Nameassociated to them by default.
- the capability, using the
IdentityService, to allocate
PublicKeys created by another node to the
CordaX500Nameof that node and optionally an "external ID" associated with that node.
- the capability to look-up the
CordaX500Name(and therefore the
Partyobject) for an
AnonymousParty(confidential identity) using the
- An account is not a node. Instead, accounts are "hosted" on Corda nodes and an account represents a logical subset of the host node's vault.
- An account doesn't have a unique identity associated with it. At the network level, the account's "identity" is the
CordaX500Nameof the host node. However, accounts are identifiable as an identity can be assigned to an account but this must be done at the application level.
- Unlike a Corda node, an account doesn't have a default
PublicKeyassociated with it. To transact with an account, you must request that the node hosting the account generate a new key pair for the account.
Do I need to add account IDs to my state objects?
No! The motivation behind the design which uses
PublicKeys mapped to account IDs is so that the notion of accounts is
not tightly coupled to state objects. The benefits are that you can use the same state objects for accounts and
non-accounts enabled workflows. To find out which account the state belongs to, you can look-up the participation/owning
keys to the account ID they are allocated to.
How do we assign identities to accounts?
This is an application level concern and it's up to you how you solve it.
How do nodes become aware of accounts?
Again, this is an application level concern. This version of account doesn't ship with any sort of automatic discovery
mechanism. However, there are primitive flows
ShareAccountInfo for sharing
AccountInfos as well
ShareStateAndSyncAccounts for sharing a state with another node, along with all the
mapping for the accounts involved in that state.
If you are writing an "accounts enabled" CorDapp you'll probably want to write a flow that shares new accounts with all the other nodes in your business network that need to be aware of the new accounts.
How do we mix workflows with accounts and non-accounts?
For now, in the interests of simplicity, we would suggest that if you need to use accounts then use accounts on all nodes on your business network. For a node that doesn't need to use accounts then you can just set up a "default" account for that node.
Writing your CorDapp if you want to have some nodes not using accounts
This is a bit tricky for now. You'll need to update your flows with logic to determine if a state participant/owner is
an account holder, or not. This can be done using the
AccountService APIs. If so, then some additional steps should be
taken to share the required
PublicKey mappings with other nodes that need to see them.
Working with accounts is much like working with confidential identities but with the addition of the
state. For example, when using confidential identities, a new
PublicKey would be requested from a counterparty node
before creating a transaction with that counterparty. The same workflow is required with accounts - a new
should be requested before creating a state and transaction. The biggest difference is that, in your flow, you must also
specify from which account the new
PublicKey should be allocated to. However, of course, this should be known up-front
by the node invoking the flow.
State selection when using accounts and unaccounted states on the same node
Currently, if accounts and non-accounts workflows are mixed on the same node then you need to be careful with state selection. When selecting states for non-accounts workflows, the state selection code will currently pick states which could be assigned to accounts. However, when selecting states for accounts workflows, you will supply the account ID for the account you want to select states from, discriminating the states query to only states held by the specified account. The best solution for now is to mandate that a node uses accounts for all states or not at all.
Are states for different accounts segregated?
No. They are all stored in the same
VaultService operated by the host node. States held by different accounts on the
same node can be partitioned by their account ID when querying the vault. See below for more info.
How do we handle permissions on a per account basis?
This must be done at the application level for now and can be handled in the RPC client part of your CorDapp.
Creating new accounts
A new account can be created by either calling the
CreateAccount flow via RPC or from an existing flow,
or by calling
CreateAccount method simply calls the
CreateAccount flow and
CordaFuture which completes when the
CreateAccount flow successfully terminates.
To create an account, you must specify an account name. the account ID is chosen randomly by the Accounts CorDapp. When
CreateAccount flow terminates, it returns a
StateAndRef<AccountInfo> for the account which has just been
CreateAccount flow does not communicate with any nodes as it performs an internal process only (a notary signature
is not required for state creation when the creating transaction does not include a
// Create account by invoking flow via RPC. val accountInfo: StateAndRef<AccountInfo> = rpcProxy.startFlow(::CreateAccount, "Roger's account").returnValue.getOrThrow() // Create account by using sub flow (from inside a flow). val accountInfo: StateAndRef<AccountInfo> = subFlow(CreateAccount("Roger's account")) // The AccountService provides access to triggering Account related flows from within a Corda Service. val accountInfo: StateAndRef<AccountInfo> = accountService.createAccount("Roger's account").getOrThrow()
Looking up an account by account ID or account name
Once you have created a new account, you can use the
AccountService to obtain the
AccountInfo by name or by account
ID using the following methods on
fun accountInfo(id: UUID): StateAndRef<AccountInfo>? fun accountInfo(name: String): List<StateAndRef<AccountInfo>>?
These methods will either return the
AccountInfo linked to the specified account ID or name or return
null if an
account cannot be found with the specified account ID or name.
// Create account from inside a flow. val accountInfo = subFlow(CreateAccount("Roger's account")) // Then look up the account by account ID and name. accountService.accountInfo(accountInfo.state.data.name) accountService.accountInfo(accountInfo.state.data.identifier.id)
Accounts can also be looked up by name and account ID via RPC using the following flows:
AccountInfoByUUID. These flows are required because
CordaServices are not accessible directly via RPC.
Creating new keys for accounts and assigning state ownership to accounts
AccountInfos are not much use by themselves. The idea behind the accounts CorDapp is that
PublicKeys can be created
and assigned to an account, then used to participate in states for the account they are assigned to.
New keys can be requested for an account by using the pair of flows called
SendKeyForAccountFlow. These flows can be used to request a new key for an account which was created on a remote node
as well as requesting a new key for an account on the same node. The flows deal with both possibilities.
A new request using
RequestKeyForAccountFlow can be made by passing in the
AccountInfo for the account you want to
request a new
PublicKey for. This means you need the
AccountInfo for the account you want a new key for before you
request the new
PublicKey. The responder flow
SendKeyForAccountFlow will be invoked as the counter-flow. A number of
possibilities can happen:
- The node running the counter-flow is the same as the requesting node, in which case, the account originated on the
requesting node. Therefore no messages need to be sent to a remote node. The key can be created locally and mapped to
the host node and account ID specified in the
- The node running the counter-flow is a remote node but it is not aware of the provided
AccountInfo. In which case, the remote node will respond that it is not aware of the account and the requesting flow will return an exception.
- The node running the counter-flow is aware of the provided
AccountInfo, so generates a new key pair and allocates the new
PublicKeyto itself as the host node and the account ID of the provided
AccountInfo. The new
PublicKeyis then sent back to the requesting node with a proof that it was generated by the node running the counter-flow. At this point, the requesting node, verifies the proof and stores a mapping of the new
PublicKeyto the host node and account ID.
RequestKeyForAccountFlow terminates, it is safe to say that you have a new key mapped to the account ID and
account host for the specified
AccountInfo and the account host has an equivalent mapping. It is also safe to say that
both the requester and the host node both have a copy of the
AccountInfo. If the requester node is the same as the
host node, of course, the prior statements hold true.
For more information on the key request protocol, take a look at the
confidential-identities CorDapp which is a dependency of the
There are startable by RPC and initiating versions of the aforementioned flows, called
Code sample for requesting a new key from an account on the same node or remote node:
// Requestor flow. // Assumption is that we already have the AccountInfo. val accountInfo: StateAndRef<AccountInfo> = accountService.accountInfo("Some account name") val newKey: AnonymousParty = subFlow(RequestKeyForAccountFlow( accountInfo = accountInfo, hostSession = initiateFlow(accountInfo.state.data.host) )) // Responder flow. subFlow(SendKeyForAccountFlow(otherSideSession))
Looking up an account by
AccountService provides an API to look-up which account a particular
PublicKey belongs to. The caveat is that
PublicKey must have been obtained using
SendKeyForAccountFlow, or manually mapped
to the account host and account ID. This API must also perform a look-up of the
AccountInfo and so will only return a
value if the associated
AccountInfo for that
PublicKey has also been stored. To look up the account ID for a
fun accountIdForKey(owningKey: PublicKey): UUID?
To look up the
AccountInfo for a
fun accountInfo(owningKey: PublicKey): StateAndRef<AccountInfo>?
// Requestor flow. // Assumption is that we already have the AccountInfo. val accountInfo: StateAndRef<AccountInfo> = accountService.accountInfo("Some account name") val newKey: AnonymousParty = subFlow(RequestKeyForAccountFlow( accountInfo = accountInfo, hostSession = initiateFlow(accountInfo.state.data.host) )) val sameAccountInfo: StateAndRef<AccountInfo> = accountService.accountInfo(newKey)
There is a startable by RPC version of this method called
Looking up a host by
You can look-up
AbstractPartys to the host
Party using an API on the
fun wellKnownPartyFromAnonymous(party: AbstractParty): Party?
This API doesn't require that the
AccountInfo associated with the
PublicKey has also been stored.
Obtaining a list of accounts
There are three APIs on the
AccountService for obtaining a list of accounts:
ourAccountswhich returns all the accounts created on the node in question.
accountsForHostwhich returns all the accounts created by a specific host. Clearly, only
AccountInfos which have been shared with the node will be returned.
allAccountswhich returns all
AccountInfos regardless of which node is the host.
There are accompanying startable by RPC flows so these functions can be invoked via RPC.
Obtaining all the
PublicKeys for an account
There is an API on
AccountService for obtaining all the
PublicKeys mapped to an account ID. This is useful if you
would like to re-use
PublicKeys instead of requesting a new
PublicKey from an account for each new transaction with
that account. In this version of accounts there's no in-built functionality for re-using
PublicKeys. However, you can
quite easily create your own flow for doing this, if it is required. To enumerate all
PublicKeys for an account, use
this API on
fun accountKeys(id: UUID): List<PublicKey>
If either the account ID is unknown or there are no
PublicKeys mapped to the account ID, then the method call will
return an empty list. Otherwise, all the keys mapped to that account will be returned. Note that this method works for
accounts created on remote nodes providing the
PublicKey mappings have previously been shared with
the calling node.
// Showing initiator flow only and assuming we already have an AccountInfo from a remote node. val accountHost: Party = someAccountInfo.host val newKeyOne: AnonymousParty = subFlow(RequestKeyForAccountFlow(accountInfo, initiateFlow(accountHost))) val newKeyTwo: AnonymousParty = subFlow(RequestKeyForAccountFlow(accountInfo, initiateFlow(accountHost))) // Should return a list _at least_ containing the above two keys. val keys: List<PublicKey> = accountService.accountKeys(someAccountInfo.identifier.id)
Requesting and sharing the
AccountInfo from/to another node by using the account ID/
This can be done using the
ShareAccountInfoFlow which are both in-line flows. There are
also initiating versions of these flows which are startable via RPC.
RequestAccountInfoFlow takes an account ID and a
FlowSession for the host of the account with the specified
account ID and returns the
AccountInfo for the account. If the host is not aware of an account with the specified
account ID, then the flow returns
ShareAccountInfoFlow is the opposite of the above flow. A node which already has an
AccountInfo can share it
with another node. The flow takes the
StateAndRef<AccountInfo> to share and a
FlowSession for the node to share it
It is important to make sure that all nodes which need
AccountInfos have them before you start transacting between
accounts, otherwise nodes in your business network will not be able to map account IDs and account hosts to
for that accounts they are not aware of.
Querying the vault by account
It is very important that we can query the vault by account. As such, there is a new property added to
VaultQueryCriteria in Corda 4.3 and it can be used like so via RPC:
rpcProxy.vaultQueryByCriteria( QueryCriteria.VaultQueryCriteria(externalIds = listOf(accountId)), StateClass::class.java )
and inside a flow:
serviceHub.vaultService.queryBy( QueryCriteria.VaultQueryCriteria(externalIds = listOf(accountId)), StateClass::class.java )
Using this approach, the vault query will only return states which have participation/owning keys which are allocated to the specified account ID. If the account ID is unknown to the node or no states are in the vault which are linked to the specified account ID, then the query will return an empty list.
The best way to learn is by looking at existing code. In that light check-out these example CorDapps which use the Accounts CorDapp:
Why use accounts?
Please see this video where Roger explains why we created accounts and the benefits of using accounts.
How does accounts work under the hood?
Please see this video where Roger explains how accounts work "under the hood".