r3 Corda中帐户之间的令牌转移

时间:2020-08-23 06:01:32

标签: corda

我一直在尝试在r3 Corda中模拟银行系统。可以找到我的项目Here。 我的系统的组件是:

  • 中央银行[为其他银行发行代币]
  • BankA
  • BankB
  • 公证人

我可以在系统中部署和运行节点。然后,我可以在这些银行下创建早午餐。分别在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

任何实施此令牌移动的具体建议都会有很大帮助!

谢谢!

1 个答案:

答案 0 :(得分:1)

  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);
  1. 取决于令牌的类型(可替代或不可替代);我会使用addMoveFungibleTokens()addMoveNonFungibleTokens()而不是addMoveTokens()
  2. 老实说,我不明白您为什么使用实用程序功能(即DatabaseTokenSelection.generateMove()addMoveTokens());如果您的交易具有多种类型的状态作为输入/输出(例如汽车代币和美元代币),并且您希望代币的交换是原子性的(要么成功要么失败),则可以使用这些属性。在您的情况下,您的交易只有一种状态类型,即令牌。您不需要所有的复杂性。只需直接使用MoveFungibleTokensFlow即可使用令牌SDK。
  3. 此外,在您的问题中,您不会分享您如何发现令牌没有移动;您是否创建了流量测试?该测试如何在移动之前和之后查询帐户的余额?
  4. Here是一个关于在2个帐户之间移动令牌的简单示例;在该示例中,唯一需要更改的是用查询条件替换null here以仅使用发送方的令牌(请参见下文);否则,该移动将消耗源节点上持有的所有令牌(您可能最终移动了属于另一个帐户的令牌;这就是为什么必须指定该查询条件的原因):
// Query vault for balance.
QueryCriteria heldBySender = new QueryCriteria.VaultQueryCriteria().withExternalIds(Collections.singletonList(accountInfo.getIdentifier().getId()));
  1. 我强烈建议您阅读Tokens SDK上的my article,甚至更重要;通过R3的官方免费 Corda课程;他们有很大一部分关于库(令牌和帐户),请参见here
  2. 您还有一个错字,就是branch;不是brunch