I am using Corda's Account library and currently unable to transfer a state from one owner (Account) to another (Account) hosted on separate nodes. Getting different errors at different time. But I am pretty sure it has to do with collecting signatures.
Is it ok to collect signature from owner account (anonymous party) or does it have to be the nodes owning key. Also, is it mandatory to collect signatures from all parties involved in the participants list in states class. For instance in the below example, is it necessary to collect signature from issue during a transfer flow.
An Issuer issues a state to an owner using Registration Flow. An owner can transfer to another owner using Transfer Flow.
I am currently having issues with Transfer Flow.
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";
}
}
State
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);
}
}