We are working with token and accounts at Corda with Kotlin. I will detail the scenario to clarify.
We have a node1 and a node2.
We created a "OurTokenType" that extends from TokenType.
We created two accounts (seller and buyer) on node1 and shared them with node2.
By the business rule of the application, node2 is the one who issues the tokens (FungibleToken), and the issue worked correctly. In the end, node1 and node2 have access to the FungibleStates for the issue transaction.
Our problem is in the transfer of these tokens. When we try to transfer the tokens from the account seller to the buyer.
In our scenario, the transfer of tokens can happen on node1 or node2.
When we transfer through node1, which is the host of the accounts, we can use the MoveFungibleTokensFlow class that works and the FungibleStates on node1 are updated to the correct value. However, when we added node2 as an observer in the observerSessions parameter, the FungibleStates on node2 were not updated correctly.
And when we do the transfer by node2, which is NOT the host of the accounts, using the MoveFungibleTokensFlow class, it doesn't work, it generates an exception of com.r3.corda.lib.tokens.selection.InsufficientBalanceException: Insufficient spendable states identified, even with balance .
My question is, can I use the MoveFungibleTokensFlow class to transfer between accounts even if they are on another host?
And when we run on the node that hosts the accounts, what is the problem to update the FungibleStates when using observerSessions?
Or if I must follow the example of the link https://github.com/corda/samples-kotlin/tree/master/Accounts/worldcupticketbooking?
MoveFungibleTokensFlow doesn't have an input parameter that specifies who's the source of the moved tokens, and that's because it considers the initiator of the flow as the source of the tokens.
class MoveFungibleTokensFlow
#JvmOverloads
constructor(
val partiesAndAmounts: List<PartyAndAmount<TokenType>>,
override val participantSessions: List<FlowSession>,
override val observerSessions: List<FlowSession> = emptyList(),
val queryCriteria: QueryCriteria? = null,
val changeHolder: AbstractParty? = null
) : AbstractMoveTokensFlow() {
You are not correct about "both node1 and node2 see the issued tokens", in FungibleToken the only participant is the holder of the tokens, so when tokens are issued to seller and buyer accounts, the only participant is the node that hosts those accounts (i.e. node1).
Going back to my first point, because only node1 has the issued tokens, and you're running the move flow on node2 (the flow consider node2 as the source of the tokens, but node2 doesn't have them); that's why you're getting the insufficient balance error.
Even if you added node2 as an observer when you issued the tokens to buyer and seller (i.e. both node1 and node2 have the resulting tokens); if you dig deep through the code of MoveFungibleTokensflow which inherits from AbstractMoveTokensFlow which relies on ObserverAwareFinalityFlow to sign the transaction, you'll see inside ObserverAwareFinalityFlow that it doesn't call CollectSignatures flow, it only signs the transaction locally (see here); meaning only the node that holds the private keys of the accounts can sign on the move command, and since node1 is the host of buyer and seller; then node1 is the owner of the private/public keys and only node1 can sign on behalf of buyer and seller. So even if you shared buyer and seller accounts with node2; only node1 can sign, and since you're calling MoveFungibleTokensFlow from node2 (your 2nd scenario), it will fail when it reaches the part where it needs to sign on behalf of seller (to move its tokens), because node2 doesn't own the private key of that account.
On a side note, when you call MoveFungibleTokesnFlow from node1 to move the tokens of seller to buyer; make sure that you supply a value for queryCriteria to select only the tokens that belong to seller; if you don't supply a value; then the flow will pick any tokens that are hosted on node1 so you might end up moving tokens that belong to other accounts.
Also make sure to supply a value for changeHolder, so that the change goes back to seller, if you keep it empty; any resulting change will be assigned to the initiating node (i.e node1) instead of the account.
You can find lots of helpful tips in my article on Tokens SDK.
Also, R3 recently released a free Corda course; it has a great section on Tokens SDK.
In order for you to be able to move tokens from seller to buyer using node1 or node2 you have to rely on the utility function addMoveTokens() instead of the ready flows (i.e. MoveFungibleTokensFlow) (because of the above mentioned reasons). So you have to create the transaction yourself, then add the tokens to be moved using addMoveTokens(), then you have to collect the signatures (of the seller) using CollectSignatures flow (the seller will sign in the responder flow); also you have to take care of finalizing the transaction.
Just to clarify the above flow, your flow should check if it's the host of the seller; then it can sign locally, if it's not the host; then you have to create a FlowSession with the host of the account so you can collect its signature (i.e. its approval to move the token that it holds).
Samples repo has some examples that use addMoveTokens(); I recommend spending some time to explore them.
Let me break down your question into two parts:
Observer session doesn't update automatically:
Here is an example of how we would trigger the update on the observer session: https://github.com/corda/samples-kotlin/blob/master/Tokens/stockpaydividend/workflows/src/main/kotlin/net/corda/samples/stockpaydividend/flows/AnnounceDividend.kt#L37
This will ensure the observer's token state is updated whenever the maintainer makes an update. (Note: only the maintainer should make the update, not the owner)
Fungible token transfer between two parties: Yes. it is doable. This line of code is the beginning of FungibleToken transfer in the worldcupticketbooking sample: https://github.com/corda/samples-kotlin/blob/master/Accounts/worldcupticketbooking/workflows/src/main/kotlin/com/t20worldcup/flows/DVPAccountsHostedOnDifferentNodes.kt#L102 It definitely works.
I see you mentioned that you had an error message on InsufficientBalanceException, if I were you, I would start digging in from there, and see which token is being queried exactly.
Related
Ok so I have an application that I inherited that we do not know the root token and we do not have any recovery keys or unseal keys. The problem is, we cannot authenticate into Vault at all and we also cannot have the instance go down.
I do have access to the datastore it uses (DynamoDB) and the encrypting keys. My assumption is that it would be possible in theory to manually add an entry and set a password directly on the underlying datastore instance so that we can have a root account again.
I know this one is weird but we cannot re-initialize the database.
Any thoughts on how this could be done?
You can try one of the below -
The initial root token generated at vault operator init time -- this token has no expiration
By using another root token; a root token with an expiration cannot create a root token that never expires
By using vault operator generate-root (example) with the permission of a quorum of unseal key holders
Root tokens are useful in development but should be extremely carefully guarded in production. In fact, the Vault team recommends that root tokens are only used for just enough initial setup (usually, setting up auth methods and policies necessary to allow administrators to acquire more limited tokens) or in emergencies, and are revoked immediately after they are no longer needed. If a new root token is needed, the operator generate-root command and associated API endpoint can be used to generate one on-the-fly.
You can read more here - https://www.vaultproject.io/docs/concepts/tokens
No matter how bad the breakup was with the previous administrator, call him and ask for the shards. Now. It's an emergency.
To create a root token, you need a quorum of shards. A shard is a large number that could be in base64. For example, this is what the same shard looks in both formats:
9PTUFNoCFapAvxQ2L72Iox/hmpjyHGC5PpkDj9itaMo=
f4f4d414da0215aa40bf14362fbd88a31fe19a98f21c60b93e99038fd8ad68ca
You can mix and match formats, but each shard can be only entered once.
Run the command vault status to know how many different shards you need to find. The default Threshold is 3:
$ vault status
Key Value
--- -----
Recovery Seal Type shamir
Initialized true
Sealed false
Total Recovery Shares 5
Threshold 3
If you do get your hands on some shards, enter the command vault operator generate-root and enter them at the prompt. Don't cancel the ongoing root token generation, if someone entered a shard some time in the past, Vault has it (even if you don't). vault operator generate-root -status will tell you if Vault already has some shards. Here is an example where the first shard of three was entered:
$ vault operator generate-root -status
Nonce 9f435314-ce20-4716-cea7-a083de224e4e
Started true
Progress 1/3
Complete false
OTP Length 26
If you can't find the shards, you are in trouble. You will have to find a password and read all the secrets one by one (can be scripted), ideally every version of them. You say you can't log in, so you might have to ask your user to do it.
Keep in mind that some backends (like the PKI) can't be exported manually, not even by root.
Regarding CollectSignatureFlow with accounts:
In the comments of worldcupticketbooking's file: DVPAccountsOnSameNode
its given Note: though buyer and seller are on the same node still we will have to call CollectSignaturesFlow as the signer is not a Party but an account.
But here its said that If your accounts are on the same node that you are running the flow on then they can all be on the signInitialTransaction, however, if one is on another node you need to use a CollectSignatureFlow
I disagree with the comment from the DVP CorDapp that you shared; if all required signers exist on the initiating node, there's no need to call CollectSignaturesFlow, instead just pass the keys of the accounts like below:
getServiceHub().signInitialTransaction(transactionBuilder,
Arrays.asList(getOurIdentity().getOwningKey(),
account1Key, account2Key, account3Key, etc...));
I am using Corda Version 4.
My CorDapp has four nodes - Notary node (validating), "Node A", "Node B" and "Node C".
Node A and Node B are to be designed as private nodes, being accessible to one user each.
Node C is a public node, accessible to 5 users.
From documentation in URL "https://github.com/corda/accounts", I understood that by using Corda Accounts the Node C can partition the vault into 5 subsets, where each subset represents an account or user.
To achieve this should I define accounts in node configuration in build.gradle?
Is there any example in the git?
RPC users are defined inside node.conf, where the user is able to connect to the node and call flows.
Accounts is something different, before Corda 4.3 a state would be owned by a party/node so for every user you had to have a node, with Accounts library they introduced a stated called AccountInfo which has a host field which is the node that hosts the account, UUID field (unique on network level), and name field (sort of a username) which is unique on host level (i.e. you can create an account devman on NodeA and account devman on NodeB and they are considered different).
Anyway, there are a lot of concepts in the Accounts library and it's not possible to answer them here, have a look at the IOU example using accounts: https://github.com/opticyclic/corda-accounts-demo
Also the library itself has 3 examples, I recommend looking at the GoldTrading one since it's the most simple one.
I just started reading/experimenting with the Accounts library yesterday and trying to wrap my head around the participants/signers part in my states.
Let's say I have the following:
1. Mint: A node that issues tokens.
2. Registry: A node that hosts accounts and generates key pairs for them when requested.
3. Wallet: A node that holds tokens on behalf of accounts.
4. I created my own fungible token which basically has an extra field: PublicKey owningAccount
The process:
1. The Registry creates a new account (let's call it Account001), so the Registry is the host of that account.
2. The Mint requests a new key pair for Account001 from Registry
3. The Mint issues a new token to Wallet and sets owningAccount to the key they got for Account001 from Registry, so now Wallet is the holder of the token
So now we have:
1. Registry is the host of Account001
2. Wallet is the holder of the token (on behalf of Account001)
Questions:
1. Is my approach of having those 3 nodes correct? One node controls the supply of tokens, another the users, and last one tracks the "balances" of tokens per user.
2. I want to keep this separation of nodes (assuming it's conceptually correct); and for that reason I don't want to include the owningAccount as part of the participants for the token, so the token will only persist in the vault of Wallet, BUT I will require owningAccount as a signer for various commands (e.g. when moving the token to a new owningAccount; both the holder (i.e. Wallet) and the owner (i.e. Registry on behalf of owningAccount) must sign).
3. In general (let's forget about tokens), if I have a node that manages users and another that manages the state that has owningAccount field, in that state do I need to have owningAccount as a participant? Like I mentioned I'm still trying to figure out the "right" approach (usually things become more clear as I program more), but I would imagine that there should be some decoupling where the owningAccount is just required as a signer for commands related to states that are tied to it, and the participant is mostly just the node to whom that state was issued to.
Roger Willis explained to me on Slack how FungibleToken allows assigning the token to a certain owner (i.e. public key) as opposed to a Party by using the Holder attribute; the process is as follows:
1. The Mint node starts the issue token flow which takes as inputs amount and AccountInfo ref
2. It requests a new public key for the referenced AccountInfo from the Accounts Registry node
3. The received public key is used to get the respective party (i.e. identityService.partyFromKey(receivedPublicKey))
4. The resulting party is assigned as the Holder of the token
5. Remember that a Party is the CordaX500Name (Accounts Registry in our case) and a public key that identifies this entity (in our case it's the public key that mapps to an AccountInfo state (i.e. to a certain user)).
6. So whenever we issue a new token, the holder will always be Accounts Registry party but the same party will have different public keys for different owners/users.
7. With all that being said we no longer need 2 nodes Accounts Registry and Wallets, we will have one node Wallets which holds our AccountInfo states and our tokens where the holder of the tokens is Wallets party but the public key in that party will vary and map to different AccountInfo states depending on who's the owner/user.
How to get information about the party that started transaction in contract code (verify)? That would be useful for implement checks like "only owner of the input state can do some action" in contract instead of flow.
Thanks. Sorry for my bad English.
This information is not contained in a Corda transaction, and therefore cannot be checked by the verify method.
Instead, you should make the owner of the state a required signer, and they should only sign if they are willing to authorise the transfer.
Additionally, you may want to put a check like the following in your flow to prevent human error:
check(ourIdentity == lenderIdentity) {
"Obligation transfer can only be initiated by the lender."
}