Reputation: 37
I have replicated the Obligation Cordapp Transfer functionality and i am struck with the linearId of Unique Identifier. I have successfully exercised the Issue Cordapp and for transfer of Obligation, i have provided the flow command with linearId of generated Obligation. The parameter which i am passing through linearId is interpreted as the External id [argument in UniqueIdentifier] instead of id and so it is unable to find the Obligation to transfer.
Here are reference for issue.
The generated id for the Obligation created is
**linearId : externalId: null
id: "4799c549-8c2b-401f-90dd-1dc115fbcfba"
Thu Mar 29 18:41:02 IST 2018>>> flow start TransferObligation$Initiator newLender: "O=PartyC,L=Paris,C=FR",anonymous: false,linearId: "4799c549-8c2b-401f-90dd-1dc115fbcfba
The passed argument, linearId has to take as id [argument in UniqueIdentifier and is randomly generated if external id is not equal to null] as per Obligation Cordapp and has to do required transfer functionality. Instead it is taking linearId passed as externalId [argument in UniqueIdentifier] and so total linearId becoming externalId_id.
>Linear Id parameter is:4799c549-8c2b-401f-90dd-1dc115fbcfba_ace60f85-b360-4cf7b198-4d3d471f9d63
>Obtaining IOU from vault.
>States Size:0
Observing that ace60f85-b360-4cf7b198-4d3d471f9d63 is appended as id which is randomly generated and unable to find the Obligation generated.
How corda will interpret linearId passed? as externalId/Id as default? In Obligation Cordapp it demonstrated in samples took as Id and required job is done.But the cordapp which i am exercising takes the passed parameter as externalId.
Or any changes has to be done in code level to take the linearId parameter as id?
Edit 1: We are developing the code in java The transfer obligation for reference.Although it is just a replicate.Please have a look at it
package com.example.flow;
import co.paralleluniverse.fibers.Suspendable;
import com.example.contract.ActivityContract;
import com.example.state.Activity;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import net.corda.confidential.IdentitySyncFlow;
import net.corda.confidential.SwapIdentitiesFlow;
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.transactions.SignedTransaction;
import net.corda.core.transactions.TransactionBuilder;
import net.corda.core.utilities.ProgressTracker;
import net.corda.core.utilities.ProgressTracker.Step;
import java.security.PublicKey;
import java.util.*;
import static com.example.contract.ActivityContract.Activity_Contract_ID;
public class ActivityTransferFlow {
@StartableByRPC
@InitiatingFlow
public static class Initiator extends ActivityBaseFlow{
public final UniqueIdentifier linearId;
public final Party newLender;
public final Boolean anonymous;
private final Step INITIATION = new Step("Obtaining IOU from vault.");
private final Step BUILDING = new Step("Building and Verifying Transaction");
private final Step SIGNING = new Step("Signing gathered transaction");
private final Step SYNCING = new Step("Syncing gathered identities "){
@Override
public ProgressTracker childProgressTracker() {
return IdentitySyncFlow.Send.Companion.tracker();
}
};
private final Step GATHERING = new Step("Gathering counterparty signatures"){
@Override
public ProgressTracker childProgressTracker() {
return CollectSignaturesFlow.Companion.tracker();
}
};
private final Step FINALISING = new Step("Finalising transaction"){
@Override
public ProgressTracker childProgressTracker() {
return FinalityFlow.Companion.tracker();
}
};
private final ProgressTracker progressTracker = new ProgressTracker(
INITIATION,
BUILDING,
SIGNING,
SYNCING,
GATHERING,
FINALISING
);
public Initiator(UniqueIdentifier linearId, Party newLender, Boolean anonymous) {
this.linearId = linearId;
this.newLender = newLender;
this.anonymous = anonymous;
}
@Override
public ProgressTracker getProgressTracker() {
return progressTracker;
}
@Suspendable
@Override
public SignedTransaction call() throws FlowException {
// step1:Retrieve Activity specified by linear id from the vault
progressTracker.setCurrentStep(INITIATION);
final StateAndRef<Activity> activityToTransfer= getObligationByLinearId(linearId);
}
final Activity inputActivity=activityToTransfer.getState().getData();
//step2:This flow can only be initiated by current recipient
final AbstractParty lenderIdentity =getLenderIdentity(inputActivity);
//step3:Abort if the borrower started this flow
if(!getOurIdentity().equals(lenderIdentity))
{
throw new IllegalStateException("Activity transfer can only be initiated by the lender.");
}
//step4:Creating the new obligation state reflecting a new lender
progressTracker.setCurrentStep(BUILDING);
final Activity transferredActivity =createOutputActivity(inputActivity);
//step4:Create transfer command
final List<PublicKey> signerKeys = new ImmutableList.Builder<PublicKey>()
.addAll(inputActivity.getParticipantKeys())
.add(transferredActivity.getLender().getOwningKey()).build();
final Command transferCommand = new Command<>(new ActivityContract.Commands.ActivityTransfer(), signerKeys);
//step5:Create a transaction builder and then add states and commands.
final TransactionBuilder builder = new TransactionBuilder(getNotary())
.addInputState(activityToTransfer)
.addOutputState(transferredActivity, Activity_Contract_ID)
.addCommand(transferCommand);
//step6:Verify and sign the transaction
progressTracker.setCurrentStep(SIGNING);
builder.verify(getServiceHub());
final SignedTransaction ptx=getServiceHub().signInitialTransaction(builder, inputActivity.getLender().getOwningKey());
//step7:Getting party object from the borrower
progressTracker.setCurrentStep(SYNCING);
final Party borrower=getBorrowerIdentity(inputActivity);
//step8:Send any keys or certificates so the signers can verify each other identity
Set<FlowSession> sessions=new HashSet<>();
Set<Party> parties= ImmutableSet.of(borrower,newLender);
for (Party party:parties) {
sessions.add(initiateFlow(party));
}
subFlow(new IdentitySyncFlow.Send(sessions,ptx.getTx(),SYNCING.childProgressTracker()));
//step9:Gathering signatures from the borrower and the new lender
progressTracker.setCurrentStep(GATHERING);
final SignedTransaction stx=subFlow(new CollectSignaturesFlow(
ptx,
sessions,
ImmutableList.of(inputActivity.getLender().getOwningKey()),
GATHERING.childProgressTracker()
));
//Step10:Notarise and record the transaction into vault and broadcast the transaction
progressTracker.setCurrentStep(FINALISING);
return subFlow(new FinalityFlow(stx,ImmutableSet.of(getOurIdentity())));
}
@Suspendable
private AbstractParty getLenderIdentity(Activity inputObligation) {
if (inputObligation.getLender() instanceof AnonymousParty) {
return resolveIdentity(inputObligation.getLender());
} else {
return inputObligation.getLender();
}
}
@Suspendable
private Activity createOutputActivity(Activity inputActivity) throws FlowException {
if (anonymous) {
final HashMap<Party, AnonymousParty> txKeys = subFlow(new SwapIdentitiesFlow(newLender));
if (!txKeys.containsKey(newLender)) {
throw new FlowException("Couldn't get lender's conf. identity.");
}
final AnonymousParty anonymousLender = txKeys.get(newLender);
return inputActivity.withNewLender(anonymousLender);
} else {
return inputActivity.withNewLender(newLender);
}
}
@Suspendable
private Party getBorrowerIdentity(Activity inputActivity) {
if (inputActivity.getBorrower() instanceof AnonymousParty) {
return resolveIdentity(inputActivity.getBorrower());
} else {
return (Party) inputActivity.getBorrower();
}
}
}
@InitiatedBy(Initiator.class)
public static class Responder extends FlowLogic<SignedTransaction> {
private final FlowSession otherFlow;
public Responder(FlowSession otherFlow) {
this.otherFlow = otherFlow;
}
@Suspendable
@Override
public SignedTransaction call() throws FlowException {
subFlow(new IdentitySyncFlow.Receive(otherFlow));
SignedTransaction stx = subFlow(new ActivityBaseFlow.SignTxFlowNoChecking(otherFlow, SignTransactionFlow.Companion.tracker()));
return waitForLedgerCommit(stx.getId());
}
}
}
Edit 2: The getObligationByLinearId method in ActivityBaseFlow and the command we use is
flow start ActivityTransferFlow$Initiator linearId: d21827b7-e4be-4874-9383-e9f339d7c9ea,newLender: "O=PartyC,L=Paris,C=FR",anonymous: false
StateAndRef<Activity> getObligationByLinearId(UniqueIdentifier linearId) throws FlowException {
System.out.println("Linear Id parameter is:"+linearId);
QueryCriteria queryCriteria = new QueryCriteria.LinearStateQueryCriteria(
null,
ImmutableList.of(linearId),
Vault.StateStatus.UNCONSUMED,
null);
List<StateAndRef<Activity>> obligations = getServiceHub().getVaultService().queryBy(Activity.class, queryCriteria).getStates();
if (obligations.size() != 1) {
System.out.println("Linear Id 1:"+linearId);
throw new FlowException(String.format("Obligation with id %s not found.", linearId));
}
//System.out.println("Linear Id 2:"+linearId);
return obligations.get(0);
}
Upvotes: 1
Views: 701
Reputation: 444
The UniqueIdentifier
constructor is taking the string input from flow start
as the externalId
.
Change your flow to accept a string instead and manually parse it using UniqueIdentifier.fromString(inputString)
. i.e
public Initiator(String inputString, Party newLender, Boolean anonymous) {
this.linearId = UniqueIdentifier.fromString(inputString);
this.newLender = newLender;
this.anonymous = anonymous;
}
Upvotes: 0