我一直在尝试在r3 Corda中模拟银行系统。可以找到我的项目Here。 我的系统的组件是:
我可以在系统中部署和运行节点。然后,我可以在这些银行下创建早午餐。分别在BankA和BankB终端中运行了以下命令:
flow start CreateAndShareAccountFlow accountName: brunchA1, partyToShareAccountInfoToList: [CentralBank, BankA, BankB]
flow start CreateAndShareAccountFlow accountName: brunchB1, partyToShareAccountInfoToList: [CentralBank, BankB, BankA]
我可以在中央银行终端为早午餐发行代币
start IssueCashFlow accountName : brunchA1 , currency : USD , amount : 80
现在,我尝试使用以下命令将令牌从brunchA1移至brunchB1。
start MoveTokensBetweenAccounts senderAccountName : brunchA1, rcvAccountName : brunchB1 , currency : USD , amount : 10
但是在BankA和BankB中运行vaultQuery之后,它根本没有转移!
run vaultQuery contractStateType : com.r3.corda.lib.tokens.contracts.states.FungibleToken
这是我的 MoveTokensBetweenAccounts 的代码段:
import co.paralleluniverse.fibers.Suspendable;
import com.r3.corda.lib.accounts.contracts.states.AccountInfo;
import com.r3.corda.lib.accounts.workflows.UtilitiesKt;
import com.r3.corda.lib.accounts.workflows.flows.RequestKeyForAccount;
import com.r3.corda.lib.tokens.contracts.states.FungibleToken;
import com.r3.corda.lib.tokens.contracts.types.TokenType;
import com.r3.corda.lib.tokens.selection.TokenQueryBy;
import com.r3.corda.lib.tokens.selection.database.config.DatabaseSelectionConfigKt;
import com.r3.corda.lib.tokens.selection.database.selector.DatabaseTokenSelection;
import com.r3.corda.lib.tokens.workflows.flows.move.MoveTokensUtilities;
import com.r3.corda.lib.tokens.workflows.utilities.QueryUtilities;
import kotlin.Pair;
import net.corda.core.contracts.Amount;
import net.corda.core.contracts.CommandData;
import net.corda.core.contracts.CommandWithParties;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.flows.*;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import java.security.PublicKey;
import java.util.*;
@StartableByRPC
@InitiatingFlow
public class MoveTokensBetweenAccounts extends FlowLogic<String> {
private final String senderAccountName;
private final String rcvAccountName;
private final String currency;
private final Long amount;
public MoveTokensBetweenAccounts(String senderAccountName, String rcvAccountName, String currency, Long amount) {
this.senderAccountName = senderAccountName;
this.rcvAccountName = rcvAccountName;
this.currency = currency;
this.amount = amount;
}
@Override
@Suspendable
public String call() throws FlowException {
AccountInfo senderAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(senderAccountName).get(0).getState().getData();
AccountInfo rcvAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(rcvAccountName).get(0).getState().getData();
AnonymousParty senderAccount = subFlow(new RequestKeyForAccount(senderAccountInfo)); AnonymousParty rcvAccount = subFlow(new RequestKeyForAccount(rcvAccountInfo));
Amount<TokenType> amount = new Amount(this.amount, getInstance(currency));
QueryCriteria queryCriteria = QueryUtilities.heldTokenAmountCriteria(this.getInstance(currency), senderAccount).and(QueryUtilities.sumTokenCriteria());
List<Object> sum = getServiceHub().getVaultService().queryBy(FungibleToken.class, queryCriteria).component5();
if(sum.size() == 0)
throw new FlowException(senderAccountName + " has 0 token balance. Please ask the Central Bank to issue some cash.");
else {
Long tokenBalance = (Long) sum.get(0);
if(tokenBalance < this.amount)
throw new FlowException("Available token balance of " + senderAccountName + " is less than the cost of the ticket. Please ask the Central Bank to issue some cash ");
}
Pair<AbstractParty, Amount<TokenType>> partyAndAmount = new Pair(rcvAccount, amount);
DatabaseTokenSelection tokenSelection = new DatabaseTokenSelection(
getServiceHub(),
DatabaseSelectionConfigKt.MAX_RETRIES_DEFAULT,
DatabaseSelectionConfigKt.RETRY_SLEEP_DEFAULT,
DatabaseSelectionConfigKt.RETRY_CAP_DEFAULT,
DatabaseSelectionConfigKt.PAGE_SIZE_DEFAULT
);
Pair<List<StateAndRef<FungibleToken>>, List<FungibleToken>> inputsAndOutputs =
tokenSelection.generateMove(Arrays.asList(partyAndAmount), senderAccount, new TokenQueryBy(), getRunId().getUuid());
Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);
TransactionBuilder transactionBuilder = new TransactionBuilder(notary);
MoveTokensUtilities.addMoveTokens(transactionBuilder, inputsAndOutputs.getFirst(), inputsAndOutputs.getSecond());
Set<PublicKey> mySigners = new HashSet<>();
List<CommandWithParties<CommandData>> commandWithPartiesList = transactionBuilder.toLedgerTransaction(getServiceHub()).getCommands();
for(CommandWithParties<CommandData> commandDataCommandWithParties : commandWithPartiesList) {
if(((ArrayList<PublicKey>)(getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners()))).size() > 0) {
mySigners.add(((ArrayList<PublicKey>)getServiceHub().getKeyManagementService().filterMyKeys(commandDataCommandWithParties.getSigners())).get(0));
}
}
FlowSession rcvSession = initiateFlow(rcvAccountInfo.getHost());
SignedTransaction selfSignedTransaction = getServiceHub().signInitialTransaction(transactionBuilder, mySigners);
subFlow(new FinalityFlow(selfSignedTransaction, Arrays.asList(rcvSession)));
return null;
}
public TokenType getInstance(String currencyCode) {
Currency currency = Currency.getInstance(currencyCode);
return new TokenType(currency.getCurrencyCode(), 0);
}
}
@InitiatedBy(MoveTokensBetweenAccounts.class)
class MoveTokensBetweenAccountsResponder extends FlowLogic<Void> {
private final FlowSession otherSide;
public MoveTokensBetweenAccountsResponder(FlowSession otherSide) {
this.otherSide = otherSide;
}
@Override
@Suspendable
public Void call() throws FlowException {
subFlow(new ReceiveFinalityFlow(otherSide));
return null;
}
}
在编写此 MoveTokensBetweenAccounts 合约时,我是否缺少任何基本知识?我关注了官方Github Samples
任何实施此令牌移动的具体建议都会有很大帮助!
谢谢!
答案 0 :(得分:1)
QueryUtilities.heldTokenAmountCriteria()
不适用于帐户;它仅适用于聚会,而您必须使用以下内容:// Query vault for balance.
QueryCriteria heldByAccount = new QueryCriteria.VaultQueryCriteria().withExternalIds(Collections.singletonList(accountInfo.getIdentifier().getId()));
QueryCriteria queryCriteria = QueryUtilitiesKt
// Specify token type and issuer.
.tokenAmountWithIssuerCriteria(tokenTypePointer, issuer)
// Specify account.
.and(heldByAccount)
// Group by token type and aggregate.
.and(QueryUtilitiesKt.sumTokenCriteria());
Vault.Page<FungibleToken> results = proxy.vaultQueryByCriteria(queryCriteria, FungibleToken.class);
Amount<TokenType> totalBalance = QueryUtilitiesKt.rowsToAmount(tokenTypePointer, results);
addMoveFungibleTokens()
或addMoveNonFungibleTokens()
而不是addMoveTokens()
。DatabaseTokenSelection.generateMove()
和addMoveTokens()
);如果您的交易具有多种类型的状态作为输入/输出(例如汽车代币和美元代币),并且您希望代币的交换是原子性的(要么成功要么失败),则可以使用这些属性。在您的情况下,您的交易只有一种状态类型,即令牌。您不需要所有的复杂性。只需直接使用MoveFungibleTokensFlow即可使用令牌SDK。null
here以仅使用发送方的令牌(请参见下文);否则,该移动将消耗源节点上持有的所有令牌(您可能最终移动了属于另一个帐户的令牌;这就是为什么必须指定该查询条件的原因):// Query vault for balance.
QueryCriteria heldBySender = new QueryCriteria.VaultQueryCriteria().withExternalIds(Collections.singletonList(accountInfo.getIdentifier().getId()));
branch
;不是brunch
。