【问题标题】:Corda Accounts Transfer State Flow IssueCorda 帐户转移状态流问题
【发布时间】:2020-07-19 23:44:43
【问题描述】:

我正在使用 Corda 的帐户库,目前无法将状态从一个所有者(帐户)转移到托管在不同节点上的另一个(帐户)。在不同的时间得到不同的错误。但我很确定这与收集签名有关。

可以从所有者帐户(匿名方)收集签名还是必须是拥有密钥的节点。此外,是否必须从州级参与者列表中涉及的所有各方收集签名。例如在下面的示例中,是否有必要在传输过程中从 issue 中收集签名。

发行者使用注册流程向所有者发布状态。 所有者可以使用 Transfer Flow 转让给其他所有者。

我目前在传输流程方面遇到问题。

传输流程

package com.template.flows;
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.template.accountutilities.NewKeyForAccount;
import com.template.contracts.CarContract;
import com.template.states.CarState;
import net.corda.core.contracts.Command;
import net.corda.core.contracts.StateAndRef;
import net.corda.core.contracts.UniqueIdentifier;
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;
import net.corda.core.node.services.vault.QueryCriteria;
import net.corda.core.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import static com.template.contracts.CarContract.CID;
import java.lang.reflect.Array;
import java.security.PublicKey;
import java.util.*;
import java.util.stream.Collectors;


@InitiatingFlow
@StartableByRPC
public class CarTransferFlowInitiator extends FlowLogic<String> {

    private final String carVin;
    private final String oldCarOwner;
    private final String newCarOwner;
    private int input;



    public CarTransferFlowInitiator(String carVin,String oldCarOwner, String newCarOwner){
        this.carVin = carVin;
        this.oldCarOwner = oldCarOwner;
        this.newCarOwner = newCarOwner;

    }

    private final ProgressTracker.Step RETRIEVING_NOTARY = new ProgressTracker.Step("Retrieving Notary");
    private final ProgressTracker.Step CREATE_TRANSACTION_INPUT= new ProgressTracker.Step("Creating Transaction Input");
    private final ProgressTracker.Step CREATE_TRANSACTION_OUTPUT= new ProgressTracker.Step("Creating Transaction Output");
    private final ProgressTracker.Step  CREATE_TRANSACTION_BUILDER= new ProgressTracker.Step("Creating transaction Builder");
    private final ProgressTracker.Step SIGN_TRANSACTION = new ProgressTracker.Step("Signing Transaction");
    private final ProgressTracker.Step INITIATE_SESSION = new ProgressTracker.Step("Initiating session with counterparty");
    private final ProgressTracker.Step FINALIZE_FLOW = new ProgressTracker.Step("Finalizing the flow");


    private final ProgressTracker progressTracker = new ProgressTracker(
            RETRIEVING_NOTARY,
            CREATE_TRANSACTION_OUTPUT,
            CREATE_TRANSACTION_BUILDER,
            SIGN_TRANSACTION,
            INITIATE_SESSION,
            FINALIZE_FLOW
    );


    @Override
    public ProgressTracker getProgressTracker() {
        return progressTracker;
    }


    public StateAndRef<CarState> checkForCarStates(UUID accountId) throws FlowException {

        // This returns the old owners unconsumed state
        QueryCriteria.VaultQueryCriteria criteria = new QueryCriteria.VaultQueryCriteria().withExternalIds(Arrays.asList(accountId)).withStatus(Vault.StateStatus.UNCONSUMED);

        //QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);

        //QueryCriteria generalCriteria = new QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED);

        List<StateAndRef<CarState>> CarStates = getServiceHub().getVaultService().queryBy(CarState.class, criteria).getStates();

        boolean inputFound = false;
        int t = CarStates.size();
        input = 0;
        for (int x = 0; x < t; x++) {
           if (CarStates.get(x).getState().getData().getCarVIN().equals(carVin)) {
           // if (CarStates.get(x).getState().getData().getLinearId().getExternalId().equals(linearId.getExternalId())) {
                input = x;
                inputFound = true;
            }
        }


        if (inputFound) {
            System.out.println("\n Input Found");


        } else {
            System.out.println("\n Input not found");
            throw new FlowException();
        }

        return CarStates.get(input);
    }


    @Suspendable
    public String call() throws FlowException {

        //Retrieve the notary identity from the network map
        progressTracker.setCurrentStep(RETRIEVING_NOTARY);
        Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);


        //<-------------------------------------- NEW CODE ------------------------------>

        // Get the oldOwnerAccount info here
        AccountInfo oldOwnerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(oldCarOwner).get(0).getState().getData();

        //Get the party here
        AnonymousParty oldOwnerAccount = subFlow(new RequestKeyForAccount(oldOwnerAccountInfo));


        // Get the newOwnerAccount info here
        AccountInfo newOwnerAccountInfo = UtilitiesKt.getAccountService(this).accountInfo(newCarOwner).get(0).getState().getData();

        //Get the party here
        AnonymousParty newOwnerAccount = subFlow(new RequestKeyForAccount(oldOwnerAccountInfo));


        //Create transaction components both input and output for this application
        progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
        StateAndRef<CarState> inputState = null;
        inputState = checkForCarStates(oldOwnerAccountInfo.getIdentifier().getId());

        System.out.println(inputState.getState().getData().getCarMake()+" "+inputState.getState().getData().getCarVIN());


        //Issuer is Toyota
        //Owner is  AutoSmart

        //AnonymousParty issuer = inputState.getState().getData().getIssuer();
        PublicKey issuerKey = inputState.getState().getData().getIssuer().getOwningKey();

        PublicKey newOwnerKey = newOwnerAccount.getOwningKey();

        //Create  transaction components both input and output for this application
        progressTracker.setCurrentStep(CREATE_TRANSACTION_OUTPUT);
        //CarState outputState = new CarState(carMake,carModel,carYear,carMileage,carVin,issuer,carOwner);

//        String carMake = inputState.getState().getData().getCarMake();
//        String carModel = inputState.getState().getData().getCarModel();
//        int carYear = inputState.getState().getData().getCarYear();
//        double carMile  = inputState.getState().getData().getCarMileAge();
//        String carVIN = inputState.getState().getData().getCarVIN();
//        AnonymousParty carIssuer = inputState.getState().getData().getIssuer();




        // This creating a new output ?
       // CarState outputState = new CarState(carMake,carModel,carYear,carMile, carVin,carIssuer,newOwnerAccount);


//        System.out.println(outputState.getParticipants());

        // Thi swill collect signature from Issuer, Old owner and New Owner


        // check what to provide here
        //List<PublicKey> requiresSigners = Arrays.asList(getOurIdentity().getOwningKey());

//        List<PublicKey> requiredSigners = inputState.getState().getData().getParticipants()
//                .stream().map(AbstractParty::getOwningKey)
//                .collect(Collectors.toList());
//        requiredSigners.add(newOwnerAccount.getOwningKey());

        List<PublicKey> requiredSigners = Arrays.asList(issuerKey,oldOwnerAccount.getOwningKey(),newOwnerKey);



        System.out.println(issuerKey+","+oldOwnerAccount.getOwningKey()+","+newOwnerKey);

        final Command<CarContract.Transfer> txCommand = new Command<>(
                new CarContract.Transfer(),
                requiredSigners
        );

        final TransactionBuilder txBuilder = new TransactionBuilder(notary)
                .addInputState(inputState)
                .addOutputState(inputState.getState().getData().withNewOwner(newOwnerAccount), CID)
                .addCommand(txCommand);

        // Create the transaction builder here and add compenents to it
        progressTracker.setCurrentStep(CREATE_TRANSACTION_BUILDER);

        // Sign the transaction
        progressTracker.setCurrentStep(SIGN_TRANSACTION);
        txBuilder.verify(getServiceHub());
        final SignedTransaction signedTx = getServiceHub().signInitialTransaction(txBuilder, getOurIdentity().getOwningKey());

        System.out.println("Current nodes identity: " +getOurIdentity().getName().getOrganisation());

        System.out.println("Signed transaction by old owner"+": "+signedTx);

        // <----------------------------------------------------------------------->
        // Code stops here
        List<FlowSession> sessions = new ArrayList<>();
        AccountInfo issuerAccount = UtilitiesKt.getAccountService(this).accountInfo("account1").get(0).getState().getData();
        sessions.add(initiateFlow(issuerAccount.getHost()));
        sessions.add(initiateFlow(newOwnerAccountInfo.getHost()));


        // Create session with counterparty
        progressTracker.setCurrentStep(INITIATE_SESSION);
        System.out.println("Owner Host name is: "+newOwnerAccountInfo.getHost().getName().getOrganisation()+" Account is: "+newOwnerAccountInfo.getName());




        System.out.println("Started session with new owner");


        SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
                signedTx, sessions, Collections.singleton(getOurIdentity().getOwningKey())));

//
//        final SignedTransaction fullySignedTx = subFlow(
//                new CollectSignaturesFlow(signedTx, sessions, CollectSignaturesFlow.Companion.tracker()));
//
//        SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
//                signedTx, sessions, CollectSignaturesFlow.tracker()));

//        SignedTransaction fullySignedTx = subFlow(new CollectSignaturesFlow(
//                signedTx, sessions));


        System.out.println("Passed the fullySignedTx section of the code.");

        //Finalizing  the transaction
        progressTracker.setCurrentStep(FINALIZE_FLOW);
        SignedTransaction sTx = subFlow(new FinalityFlow(fullySignedTx,sessions));

        System.out.println("Getting sign from initiator");
        return "Transfer Completed";
    }
}

package com.template.states;

import com.template.contracts.CarContract;
import net.corda.core.contracts.BelongsToContract;
import net.corda.core.contracts.ContractState;
import net.corda.core.contracts.LinearState;
import net.corda.core.contracts.UniqueIdentifier;
import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty;
import net.corda.core.serialization.ConstructorForDeserialization;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


@BelongsToContract(CarContract.class)

public class CarState implements ContractState, LinearState {

    private String carMake;
    private String carModel;
    private int carYear;
    private double carMileAge;
    private String carVIN;
    private AnonymousParty issuer;
    private AnonymousParty owner;
    private UniqueIdentifier linearId;
    private List<AbstractParty> participants;


    public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, AnonymousParty issuer, AnonymousParty owner) {
        this.carMake = carMake;
        this.carModel = carModel;
        this.carYear = carYear;
        this.carMileAge = carMileAge;
        this.carVIN = carVIN;
        this.issuer = issuer;
        this.owner = owner;
        this.linearId = new UniqueIdentifier();

    }

    @ConstructorForDeserialization
    public CarState(String carMake, String carModel, int carYear, double carMileAge, String carVIN, AnonymousParty issuer, AnonymousParty owner, UniqueIdentifier linearId) {
        this.carMake = carMake;
        this.carModel = carModel;
        this.carYear = carYear;
        this.carMileAge = carMileAge;
        this.carVIN = carVIN;
        this.issuer = issuer;
        this.owner = owner;
        this.linearId = linearId;

    }


    public String getCarMake() {
        return carMake;
    }

    public String getCarModel() {
        return carModel;
    }

    public int getCarYear() {
        return carYear;
    }

    public double getCarMileAge() {
        return carMileAge;
    }

    public String getCarVIN() {
        return carVIN;
    }

    public AnonymousParty getIssuer() {
        return issuer;
    }

    public AnonymousParty getOwner() {
        return owner;
    }



    @NotNull
    @Override
    public List<AbstractParty> getParticipants() {
        return Arrays.asList(issuer,owner);

    }

    @NotNull
    @Override
    public UniqueIdentifier getLinearId() {
        return linearId;
    }

    public CarState withNewOwner(AnonymousParty newOwner){
        return new CarState(carMake,carModel,carYear,carMileAge, carVIN,issuer,newOwner, linearId);
    }


}

【问题讨论】:

    标签: java corda


    【解决方案1】:

    你的流程中有很多东西需要改变:

    1. 您由车主搜索汽车然后循环遍历列表直到找到具有所需 VIN 的状态的方法不好。如果那个账户有 100,000 辆汽车呢?您将遍历 100,000 条记录,直到找到具有所需 VIN 的记录?相反,为您的汽车状态创建一个自定义架构,以便您可以创建一个按 VIN 过滤的查询条件。查看有关如何创建自定义架构 hereherehere 的示例。
    2. 您的汽车状态应该实现LinearState 而不仅仅是ContractState。这将允许您“更新”您的状态。 LinearState 有一个 linearId 属性,该属性在网络级别是唯一的。因此,当您想要更新汽车时,您将消费作为输入,然后创建更新的版本作为输出;输出汽车必须具有完全相同的linearId,这样您就可以跟踪该汽车的不同版本,因为它会更改其属性值(您可以通过该linearId 查询汽车)。
    3. 在第 2 步之后,您的汽车状态应该有 2 个构造函数;一个不将linearId 作为输入参数并在构造函数内部为它分配一个随机值的构造函数(当您发出汽车时使用此构造函数,使用该构造函数创建输出状态);那么你需要第二个构造函数,它以linearId 作为输入参数,并标有@ConstructorForDeserialization 注释;此构造函数用于创建输出汽车(即拥有新所有者的汽车)时的传输,您将向其传递输入汽车的linearId;这样,输出汽车就被视为输入汽车的更新版本(因为它们具有相同的linearId)。
    4. 关于您关于所需签名者的问题;由您决定谁是合同中每个命令所需的签名者(您没有在问题中分享代码)。从逻辑上讲,当你发行新车时,应该只需要发行人;当您转让汽车时,只需当前(旧)车主签字。
    5. 假设转移流程的发起者是汽车的当前所有者(您不希望有人在未经您同意的情况下转移您的汽车)。因此,在您的情况下,流程的发起者(即旧所有者)是唯一需要的签名者(如果这是您的合同所说的话);所以在本地签署交易就足够了。
    6. 你仍然需要用新所有者的节点创建一个FlowSession;以便最终流发送最终确定的交易/状态,而响应者流(即新所有者)接收最终确定的交易/状态。

    【讨论】:

    • 我已将我的 CarState 更新为 LinearContract,还对 TransferFlow 进行了更改(请参阅 TransferFlow 的编辑部分)。但仍然出现无法将密钥匹配到会话以从中收集签名的错误。修复此问题后,我将使用架构。
    • 还更新了上面的 CarState 部分以反映新的变化
    • 如果新旧所有者的账户在同一个节点上,并且是旧所有者的节点发起了这个流程(应该);那么您不需要FlowSession 来作为新所有者。您可以通过在列表中提供新旧所有者的公钥作为 signInitialTransaction() 的第二个参数来在本地签名。
    • 请记住,节点通过使用该帐户的密钥进行签名来代表该帐户进行签名。因此,如果新旧所有者都托管在启动流程的节点上;然后你可以signInitialTransaction(list(old owner key, new owner key))(当然这是伪代码;找到合适的Java语法)。
    • 但是 oldOwner 和 newOwner 在不同的节点上。基本上,Issuer、oldOwner 和 newOwner 都在不同的节点上。在这种情况下应该怎么做。
    猜你喜欢
    • 2021-10-01
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-09
    • 2011-09-25
    • 2021-04-26
    • 1970-01-01
    相关资源
    最近更新 更多